En este trabajo vamos a analizar un conjunto de datos con información asociada a propiedades en venta. Nuestro objetivo será explicar el precio de venta de dichas propiedades y para tal fin realizaremos un modelo lineal múltiple.

Preparación de los Datos

Comenzamos cargando las librerías Tidyverse y Lubridate

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.3.2     ✓ purrr   0.3.4
✓ tibble  3.0.3     ✓ dplyr   1.0.2
✓ tidyr   1.1.2     ✓ stringr 1.4.0
✓ readr   1.3.1     ✓ forcats 0.5.0
── Conflicts ────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(lubridate)

Attaching package: ‘lubridate’

The following objects are masked from ‘package:base’:

    date, intersect, setdiff, union

Cargamos los datasets de train y test. Le damos un vistazo al dataset de train.

properties_train <- read.csv('ar_properties_train.csv')
properties_test <- read.csv('ar_properties_test.csv')
glimpse(properties_train)
Rows: 32,132
Columns: 8
$ id              <fct> r22OfzZ3kXooSPoE5HMuZQ==, atZQXVtyfG7+OiX6gYY3lA==, Tur/v/qE41UBT83NM/bI7w==, PNtNl6s…
$ l3              <fct> Almagro, Almagro, Almagro, Almagro, Almagro, Almagro, Almagro, Palermo, Palermo, Pale…
$ rooms           <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2…
$ bathrooms       <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1…
$ surface_total   <int> 40, 49, 40, 40, 49, 40, 40, 40, 36, 90, 54, 40, 40, 40, 36, 38, 32, 75, 31, 53, 39, 4…
$ surface_covered <int> 37, 44, 37, 37, 44, 37, 37, 34, 33, 90, 48, 40, 34, 36, 33, 34, 29, 68, 28, 50, 34, 3…
$ price           <int> 92294, 115000, 88900, 88798, 110975, 99000, 96984, 99500, 121600, 285000, 140000, 110…
$ property_type   <fct> Departamento, Departamento, Departamento, Departamento, Departamento, Departamento, D…

Hagamos una mejor visualización de los datos de entrenamiento usando la librería knitr.

library(knitr)
library(kableExtra)
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio

Attaching package: ‘kableExtra’

The following object is masked from ‘package:dplyr’:

    group_rows
properties_train %>%  
  head() %>% 
  kable() %>% 
  kable_styling(bootstrap_options = c("striped"))
id l3 rooms bathrooms surface_total surface_covered price property_type
r22OfzZ3kXooSPoE5HMuZQ== Almagro 1 1 40 37 92294 Departamento
atZQXVtyfG7+OiX6gYY3lA== Almagro 1 1 49 44 115000 Departamento
Tur/v/qE41UBT83NM/bI7w== Almagro 1 1 40 37 88900 Departamento
PNtNl6sWjHEyRPQAeRAIgw== Almagro 1 1 40 37 88798 Departamento
Lhg4dNG9VmPbHt0Swy2ATA== Almagro 1 1 49 44 110975 Departamento
oeLhuwomY2yj4A9TtDrgCA== Almagro 1 1 40 37 99000 Departamento

Veamos el tipo de cada variable usando summary.


summary(properties_train)  
                        id                   l3            rooms         bathrooms     surface_total
 //E++5/CZfagBuy/nNSY6w==:    1   Palermo     : 4890   Min.   :1.000   Min.   :1.000   Min.   : 28  
 //HmOjf8HY92NrHdGhIkcQ==:    1   Belgrano    : 2736   1st Qu.:2.000   1st Qu.:1.000   1st Qu.: 46  
 //LGSHflp8hGTvVyayEzNA==:    1   Almagro     : 2486   Median :3.000   Median :1.000   Median : 65  
 //pMfrku8dzS6Ln+dQO86g==:    1   Caballito   : 2393   Mean   :2.722   Mean   :1.427   Mean   : 80  
 //qk8tRdH6OfkWPujmTckQ==:    1   Villa Crespo: 2095   3rd Qu.:3.000   3rd Qu.:2.000   3rd Qu.: 99  
 //w2yC4PpfGEQEMB0N2BuQ==:    1   Recoleta    : 1824   Max.   :8.000   Max.   :5.000   Max.   :320  
 (Other)                 :32126   (Other)     :15708                                                
 surface_covered      price             property_type  
 Min.   : 26.00   Min.   : 69500   Casa        :  809  
 1st Qu.: 42.00   1st Qu.:120000   Departamento:28203  
 Median : 58.00   Median :169000   PH          : 3120  
 Mean   : 70.61   Mean   :214991                       
 3rd Qu.: 85.00   3rd Qu.:260000                       
 Max.   :265.00   Max.   :950000                       
                                                       

El dataset de entrenamiento tiene 8 variables. Vamos a describirlas una por una

id es una variable categórica que identifica a cada usuario, y por ende, no tiene valores repetidos. l3 Es una variable categórica que representa el barrio de la propiedad. A simple vista podemos deducir que posee al menos 10 valores distintos, siendo Palermo, Belgrano, Almagro, Caballito, Villa Crespo y Recoleta los más frecuentes. rooms es una variable numérica que toma valores enteros del 1 al 8 y representa la cantidad de habitaciones de la propiedad. Bathrooms toma valores del 1 al 5 y representa la cantidad de baños. surface_total y surface_covered miden la superficie total y la superficie cubierta respectivamente, que asumimos que están medidas en metros cuadrados. Posiblemente estas variables tomen valores enteros, aunque no sería necesario que lo hagan. price determina el precio en dolares, y property_type es una variable categórica que toma 3 valores distintos: Casa, PH y Departamento (el más frecuente).

Para concluir el análisis de nuestros datos, veamos la correlación de las variables numéricas, para comenzar a ver relaciones que aparezcan entre las variables. Vamos a usar la correlación de Spearman dado que no etrajimos outliers de los datos.

datos_train_numericos <- properties_train %>%
  select(rooms,bathrooms, surface_total, surface_covered, price)

cor(datos_train_numericos, method = 'spearman')  
                    rooms bathrooms surface_total surface_covered     price
rooms           1.0000000 0.5988559     0.8295580       0.8643980 0.6847613
bathrooms       0.5988559 1.0000000     0.6582640       0.6742340 0.6595851
surface_total   0.8295580 0.6582640     1.0000000       0.9594813 0.8296869
surface_covered 0.8643980 0.6742340     0.9594813       1.0000000 0.8274797
price           0.6847613 0.6595851     0.8296869       0.8274797 1.0000000

Podemos encontrar una correlación positiva entre todas las variables ya que están atadas al tamaño de la propiedad: Cuanto más grande sea una propiedad, mayor será la cantidad de habitaciones, baños, superficie y el precio de la misma. Destacamos la fuerte correlación que hay entre ambas variables de superficie (0.959). Estas dos variables también presentan una correlación bastante alta con la cantidad de habitaciones (0.829 para surface_total y 0.864 para surface_covered). Las variables de superficie también se correlacionan bastante con el precio. Todo esto será tenido en cuenta más adelante a la hora de realizar los modelos lineales.

Primeros modelos de Regresión lineal múltiple

Realicemos un modelo inicial usando todas las variables para predecir el precio

library(tidymodels)
── Attaching packages ────────────────────────────────────────────────────────────────────── tidymodels 0.1.1 ──
✓ broom     0.7.1      ✓ recipes   0.1.13
✓ dials     0.0.9      ✓ rsample   0.0.8 
✓ infer     0.5.3      ✓ tune      0.1.1 
✓ modeldata 0.0.2      ✓ workflows 0.2.0 
✓ parsnip   0.1.3      ✓ yardstick 0.0.7 
── Conflicts ───────────────────────────────────────────────────────────────────────── tidymodels_conflicts() ──
x scales::discard()        masks purrr::discard()
x dplyr::filter()          masks stats::filter()
x recipes::fixed()         masks stringr::fixed()
x kableExtra::group_rows() masks dplyr::group_rows()
x dplyr::lag()             masks stats::lag()
x yardstick::spec()        masks readr::spec()
x recipes::step()          masks stats::step()
modelo_total <- lm(price ~ l3 + rooms + bathrooms + surface_total + surface_covered + property_type, data = properties_train)
tidy_total <- tidy(modelo_total, conf.int = TRUE)
tidy_total

Analicemos las variables dummies. Empecemos viendo los valores que toma la variable l3

unique(properties_train %>%
         select(l3))

Los barrios en el modelo estarían ordenados alfabéticamente. Se puede observar que no hay una dummie con el barrio del Abasto, que sería el primer barrio en orden alfabético. Esto quiere decir que Abasto es la categoría basal, en otras palabras, de valer 0 todas las variables dummies relacionadas a la localidad, estaríamos viendo el precio esperado de una propiedad del Abasto (en función de los valores de las variables que no involucran a la localización). El coeficiente Beta estimado respectivo de cada propiedad nos muestra entonces la diferencia en precio promedio al comprar una propiedad en esa localidad y de comprarla en el Abasto (es decir, todas las otras variables se mantengan fijas). Así, si tuviesemos dos propiedades similares, una en agronomía, y otra en el Abasto, la pripiedad de Agronomia valdría, en promedio, 1241 dolares más que la propiedad del Abasto.

Respecto a la significatividad de los coeficientes, Veamos el p-valor y los intervalos de confianza.

tidy_total %>%
  select(term, p.value, conf.low, conf.high)

Se puede obseervar que aproximadamente la mitad de las variables dummies generadas por la variable categórica l3 no son significativas, ya que su p-valor es mayor a 0.05 y los intervalos de confianza contienen al 0. Por ejemplo, la variable l3Agronomía tiene un p-valor de 0.907 y su intervalo de confianza contiene al cero. Por ende, no tenemos evidencia estadísticamente significativa de que una propiedad de Balvanera tenga un precio esperado diferente al de una propiedad del Abasto.

Esto nos da un indicio de que posiblemente la variable categórica l3 no sea beneficiosa para el modelo, por lo que sería aconsejable quitarla o transformarla de algún modo. Esto lo veremos más en profundidad cuando veamos el test F. Respecto a las otras variables del dataset de entrenamiento, los p-valores son extremadamente pequeños y sus intervalos de confianza están muy alejados del 0, así que nuestra conclusión es que la inclusión de esas variables sí es significativa en el modelo.

La otra variable categórica, property_type, tiene 3 valores posibles: Casa, Departamento y PH. Como no hay una Dummy con Casa, entonces si las variables property_typeDepartamento y property_typePH valiesen 0, estaríamos viendo el precio promedio de una casa en función de las valores que toman las variables del modelo que no involucran a la propiedad. Los coeficientes Beta estimados de property_typeDepartamento y property_typePH muestran la diferencia esperada del precio de un departamento y de un PH respecto del precio de una casa con similares características. Así por ejemplo, si tuviesemos un departamento y una casa ubicados en la misma localidad, con similar cantidad de habitaciones, baños, y que ocupan y cubran superficies similares, entonces el departamento valdría, en promedio, 91485 dolares más de lo que valdría la casa.

Hay que tener en cuenta que, de todos modos, los departamentos en general no son tan grandes como una casa, así que es dificil que estén en igualdad de condiciones. Además, como este modelo no presenta interacción entre variables, no podemos comparar el efecto, por ejemplo, de aumentar el número de habitaciones en el precio de un departamento y en el precio de una casa, ya que al no haber interacción, nuestro modelo interpretaróa que la pendiente del precio respecto de la variable rooms no depende del tipo de propiedad.

Respecto a los otros coeficientes, se puede esperar que a cuanto mayor sea el número de baños, habitaciones o superficie que ocupa o que cubre la propiedad, más alto será el precio de la misma. El modelo sin embargo nos dice que el estimador de la pendiente respecto de la variable rooms es -4360. Esto significa que si dos propiedades son idénticas, salvo que una tiene una habitación más que la otra, entonces se espera que la propiedad con una habitación más cueste 4360 dólares menos. Este extraño resultado puede deberse a que la variable rooms esté muy correlacionada con las variables de superficie, lo que hace que el modelo interprete erróneamente que aumentar el número de habitaciones repercute negativamente en el precio de una propiedad. Lo que sucede en realidad es que no es realisticamente posible aumentar el número de habitaciones manteniendo las otras variables fijas, debido a la correlación positiva que hay con las otras variables del dataset, principalmente las variables de superficie.

Analicemos ahora el coeficiente F.

tidy(anova(modelo_total))

La tabla de anova nos muestra que, según el resultado del test F, todas las variables son estadísticamente significativas, ya que todos los p-valores dan prácticamente 0.

Si bien obtuvimos que la variable l3 es estadísticamente significativa, sospechamos que esta variable puede repercutir negativamente en el modelo, se puede ver, por ejemplo, que los errores estándares de estas variables son considerablemente altos. Vamos a realizar otro modelo sin la variable l3 y comparemos los resultados obtenidos en ambas regresiones.

modelo_sin_l3 <- lm(price ~  rooms + bathrooms + surface_total + surface_covered + property_type, data = properties_train)
tidy_sin_l3 <- tidy(modelo_sin_l3, conf.int = TRUE)
tidy_sin_l3

Todos los p-valores obtenidos dan muy por debajo de 0.05, y los intervalos de confianza están muy lejos del 0. Con esto podemos concluir que todas las variables son estadísticamente significativas. Podemos observar la tabla de Anova para confirmar esto con la variable categórica property_type.

tidy(anova(modelo_sin_l3))

Efectivamente, el test F rechaza la hipótesis de que la pendiente del precio de una propiedad respecto de la variable property_type es igual a 0.

Respecto a los valores estimados de los parámetros Beta, nuevamente destacamos que el parámetro asociado a la variable rooms es negativo: Si tenemos dos propiedades que difieren únicamente en que una tiene una habitación más que la otra, entonces la propiedad con una habitación más esperamos que valga 13657 dólares que la otra habitación. Este valor da incluso mayor que el coeficiente de la variable de rooms que observamos previamente.

Comparemos la efectividad de ambos modelos. Como el modelo que usa todas las variables va a ajustar mejor por tener más opciones para optimizar, utilizaremos el coeficiente de R² ajustado y no el R² común sin ajustar. De esta forma vamos a tener una forma más justa de medir que efecto tiene agregar la variable l3 en nuestro modelo.

library(broom)
models <- list(modelo_total = modelo_total,modelo_sin_l3 = modelo_sin_l3)
df_evaluacion_train = map_df(models, broom::glance, .id = "model") %>%
  # ordenamos por R2 ajustado
  arrange(desc(adj.r.squared))

df_evaluacion_train
NA

Se puede observar que los valores ajustados de R² no difieren mucho de los valores de R² sin ajustar. Como el valor ajustado de R² es mejor en el modelo total, concluimos que considerar la localidad de la propiedad tiene un efecto positivo a la hora a explicar la variabilidad del precio. En otras palabras, el modelo que considera a todas las variables explica mejor la variabilidad del precio que el modelo que no considera la variable l3.

#Creación de variables

Para crear un modelo superior a los modelos que vimos antes podemos crear nuevas variables. Una forma de hacerlo es extraer información de una fuente externa. Nos vamos a limitar a crear variables usando información de las variables que ya tenemos.

Nos interesaría agrupar a los barrios para que sean más significativos. Vamos a agruparlos según el precio por metro cuadrado promedio. Para eso podríamos sumar todos los precios de las propiedades de cada barrio y dividirlos por el total de superficie en metros cuadrados de todas esas propiedades. Otra opción sería calcular el precio por metro cuadrado de cada propiedad y luego agrupar por localidad y promediar. Vamos a utilizar la segunda opción que a nuestro criterio calcula un promedio más honesto.


properties_train <- properties_train %>%
  mutate(precio_superficie = price/surface_total)

Promedio_barrios <- unique(properties_train %>%
  group_by(l3) %>%
  mutate(precio_barrio = mean(precio_superficie)) %>%
  select(l3,precio_barrio)) 

Promedio_barrios  
NA

Para agrupar a los barrios utilizaremos los cuartiles de precio_barrio

q <- quantile(Promedio_barrios$precio_barrio)
q
      0%      25%      50%      75%     100% 
1239.104 2090.678 2205.028 2587.910 5438.285 

Primero precio_barrio al dataset de entrenamiento. Una vez hecho esto podemos quitar la variable precio_superficie ya que no la volveremos a utilizar

properties_train <-properties_train %>%
  group_by(l3) %>%
  mutate(precio_barrio = mean(precio_superficie)) %>%
  select(id,l3,rooms,bathrooms,surface_total,surface_covered,price,precio_barrio,property_type)
colnames(properties_train)
[1] "id"              "l3"              "rooms"           "bathrooms"       "surface_total"   "surface_covered"
[7] "price"           "precio_barrio"   "property_type"  

Los barrios cuyo precio promedio por metro cuadrado serán clasificados como barrios de precio bajo, los que se encuentren el en segundo y tercer cuartil serán clasificados de precio medio y los restantes serán clasificados commo barrios de precio alto. De esta forma la mitad de los barrios serán considerados de precio medio.

properties_train <- properties_train %>%
  mutate(barrios = case_when(precio_barrio <= q[2] ~ 'precio_bajo',
                             precio_barrio > q[4] ~ 'precio_alto',
                             TRUE ~ 'precio_medio'))

properties_train

Realicemos un modelo cosiderando la variable barrios en lugar de l3

modelo_barrios <- lm(price ~  rooms + bathrooms + surface_total + surface_covered + property_type + barrios, data = properties_train)
tidy_barrios <- tidy(modelo_barrios, conf.int = TRUE)
tidy_barrios

Se puede observar que todos los p-valores correspondientes a cada parámetro son muy pequeños, incluyendo las dos variables dummy agregadas: barriosprecio_bajo y barriosprecio_medio. Esto significa que los estimadores de cada beta son significativamente distintos de 0. Se puede observar que el estimador del Beta de la variable rooms sigue siendo negativo, por ende nuestros problemas de colinealidad siguen persistiendo.

Respecto de las nuevas categorías agregadas, se puede observar que precio_alto es la categoría basal ya que no hay una dummy barriosprecio_alto. Esto quiere decir que, de valer 0 las variables barriosprecio_bajo y barriosprecio_medio, entonces la propiedad a la que queremos estimar el precio es una propiedad ubicada en un barrio con alto promedio de precio por metro cuadrado. Los estimadores de los betas correspondientes a cada tipo de barrio nos muestras cuanto varía en promedio el precio de una propiedad de ese tipo respecto del precio de una propiedad ubicada en un barrio con alto precio por metro cuadrado. Así, por ejemplo, una propiedad de Parque Chacabuco, barrio de precios bajos, costaría, en promedio, 85702 dolares menos que una propiedad de similares características pero ubicada en Palermo, barrio de precios altos.

Analicemos ahora el test F de Anova

tidy(anova(modelo_barrios))
NA

Los p-valores que arroja el test son prácticamente 0, por lo que el test F nos dice que el modelo es válido.

Veamos ahora qué tan bueno es este modelo explicando la variabilidad de los datos en comparación a los dos modelos que habíamos creado previamente.

models <- list(modelo_total = modelo_total,modelo_sin_l3 = modelo_sin_l3, modelo_barrios = modelo_barrios)
df_evaluacion_train = map_df(models, broom::glance, .id = "model") %>%
  # ordenamos por R2 ajustado
  arrange(desc(adj.r.squared))

df_evaluacion_train
NA

El modelo con todas las variables originales sigue explicando mejor la variabilidad de los datos de acuerdo al coeficiente de R² ajustado. Se puede observar de todos modos que la diferencia entre el R² y el R² ajustado no es muy alta debido al gran número de observaciones que tenemos disponibles, lo que hace que el efecto nocivo de agregar variables irrelevantes a los datos no se vea tan reflejado.

El modelo nuevo presenta una mejora considerable respecto al modelo sin la variable l3. Además, a diferencia del modelo con todas las variables, la inclusión de todas las variables dummy es significativa, lo que le da al modelo cierta robustez. Otra ventaja del modelo nuevo es que es mucho más sencillo que el modelo donde usabamos todas las dummies de los barrios.

La única desventaja del modelo nuevo, además de su más bajo R², es que la variable categórica barrios depende del precio de las propiedades del conjunto de entrenamiento, lo que podría hacer que un barrio con no demasiadas observaciones termine en una categoría incorrecta por los datos de entrenamiento que hay disponibles. De todos modos, un barrio con pocas observaciones podría repercutir aún mucho peor en el modelo con las dummies por cada barrio, ya que el estimador para esa dummy daría muy inexacto.

La variables l3 y barrios no se podrían generalizar para barrios nuevos, pero sería más fácil arreglar este problema en la variable barrios ya que solo tendríamos que identificar si el barrio nuevo tiene precios altos, bajos o medianos mediante alguna muestra dada sin necesidad de armar un modelo nuevo para ese barrio.

En conclusión, considero que el modelo que utiliza la variable barrio puede ser más útil que el que considera la varialble l3 por su simpleza, a pesar de que el coeficiente de R² ajustado sea menor.

Para solucionar nuestros problemas de colinealidad, vamos a armar un nuevo modelo utilizando, en vez de surface_total, una variable que mida la superficie no cubierta. Esto lo hacemos porque surface_total está muy correlacionada con surface_covered

properties_train <-
  properties_train %>%
  mutate(superficie_descubierta = (surface_total-surface_covered))

Veamos los resultados de incluir la variable superficie_descubierta al modelo en lugar de surface_total

modelo_descubierta <- lm(price ~  rooms + bathrooms + superficie_descubierta + surface_covered + property_type + barrios, data = properties_train)
tidy_descubierta <- tidy(modelo_descubierta, conf.int = TRUE)
tidy_descubierta

Analizando los p-valores obtenidos, notamos que todas las variables consideradas son estadísticamente significativas. Nuevamente el estimador de la pendiente del precio en función de la variable rooms es negativo. Los estimadores de los betas de surface_covered y superficie_descubierta son 2529 y 851 respectivamente. Esto significa que, por cada metro cuadrado de superficie cubierta agregado, el precio de una propiedad aumenta, en promedio, 2529 dólares; mientras que si agregamos un metro cuadrado de superficie descubierta el precio aumenta, en promedio, 851 dólares, que es aproximadamente la tercera parte. Se puede ver, de hecho, que los estimadores dan igual en los dos modelos para todas las variables excepto surface_covered, y el estimador de la pendiente del precio respecto de surface_total y superficie_descubierta coinciden, y. Además, la diferencia entre los estimadores del beta de surface_covered en ambos modelos es el estimador del beta de superficie_descubierta. Dicha diferencia se debe a que en el modelo donde consideramos surface_total estamos contando la superficie descubierta 2 veces al considerar tanto surface_covered como surface_total. Así que escencialmente ambos modelos son iguales.

Veamos rápidamente el test F para comprobar la validez del modelo.

tidy(anova(modelo_descubierta))
NA

Los p-valores son todos prácticamente 0. El modelo según el test F es válido.

Ahora comparemos los coeficientes de R² ajustado de cada modelo

models <- list(modelo_total = modelo_total,modelo_sin_l3 = modelo_sin_l3, modelo_barrios = modelo_barrios, modelo_descubierta = modelo_descubierta)
df_evaluacion_train = map_df(models, broom::glance, .id = "model") %>%
  # ordenamos por R2 ajustado
  arrange(desc(adj.r.squared))

df_evaluacion_train
NA

Los coeficientes de R² ajustado de ambos modelos coinciden. Esto se debe a que ambos modelos escencialmente son lo mismo. De todos modos, el modelo con superficie descubierta es más fácil de interpretar ya que no presenta solapamientos entre las variables de superficie.

#Diagnóstico del modelo de superficie descubierta

En esta sección observaremos los residuos del modelo para corroborar que se verifican los supuestos del mismo. Observemos el diagnóstico del modelo donde consideramos la variable barrios en lugar de l3 y la variable superficie_descubierta en lugar de surface_total.

plot(modelo_descubierta)

Residous vs valores predichos: Para que los supuestos se cumplan, los residuos no deberían tener estructura. El gráfico de residuos vs valores predichos debería formar una nube uniforme de puntos. Se puede ver, sin embargo, que los residuos varían más para valores predichos medianamente grandes (al rededor de 400000).

QQ plot: Los extremos inferior izquierdo y superior derecho no se ajustan a la distribución teórica.

Residuos estandarizados vs valores predichos: La pendiente del gráfico es positiva, lo que muestra que los residuos estandarizados tienen una tendencia a aumentar a medida que aumenta el valor predicho.

Residuos vs Leverage: Hay una observación que tiene un leverage muy alto. El residuo asociado a ese valor es bastante alto también, lo que indica que ese valor podría repercutir mucho en la calidad del modelo.

Busquemos la observación con alto leverage

au_modelos = purrr::map_df(models, broom::augment, .id = "model")

au_modelos %>% filter(model == "modelo_descubierta") %>%
  filter(.hat == max(.hat))

Pareciera ser una observación con valores faltantes en l3 y en surface_total (y por ende, en superficie_descubierta).

Debido a la presencia de observaciones con alto leverage, la falta de normalidad y la falta de homocedasticidad detectada, concluimos como diagnóstico que el modelo de superficie descubierta no satisface los supuestos del modelo lineal.

#Modelo con Logaritmo

En esta sección vamos a crear un modelo lineal usando variables escaladas logarítmicamente. El objetivo de este análisis es remover las limitaciones del modelo lineal para explicar la variación del precio cuando dichas variaciones, en función de las variables que tenemos a nuestra disposición, son exponenciales. Las variables que vamos a componer con logaritmo son price, rooms, bathrooms y surface_covered. También usaremos las variables categóricas property_type y barrios y la variable numérica superficie_descubierta, la cual no le tomamos logaritmo porque podrían haber problemas de dominio si esta toma el valor 0.

Comencemos creando las variables logartimo.

properties_train <- properties_train %>%
  mutate(log_price = log(price), log_rooms = log(rooms), log_bathrooms = log(bathrooms), log_surface_covered = log(surface_covered))

head(properties_train)

Procedamos entonces a generar el modelo y estudiar los resultados del mismo

modelo_log<- lm(log_price ~  log_rooms + log_bathrooms + superficie_descubierta + log_surface_covered + property_type + barrios, data = properties_train)
tidy_log <- tidy(modelo_log, conf.int = TRUE)
tidy_log

Se puede observar que los p-valores de cada término son muy pequeños y los intervalos de confianza están muy alejados del 0. Observemos también el test F para asegurarnos que el modelo con logaritmo tiene validez

´

tidy(anova(modelo_log))
NA

La tabla de anova muestra que los p-valores son prácticamente todos 0, lo que valida los resultados que podemos interpretar del modelo.

Como estamos tomando logaritmo sobre el precio, es de esperar que las estimaciones de la pendiente del logaritmo del precio respecto de cada variable del modelo disminuyan considerablemente. Así, por ejemplo, el estimador del beta de log_rooms sigue siendo negativo pero esta vez es de tan solo -0.05. Para que el logaritmo de rooms suba 1 necesitaríamos que el número de habitaciones sea multiplicado por el número e (es decir, aproximadamente por 2.718). Dado que la cantidad de habitaciones es un número entero, es raro que esto suceda, pero podemos dar un ejemplo aproximado.

Supongamos que tenemos una propiedad con 4 habitaciones, y otra con 11 habitaciones. 11 es 4 * 2.75, es decir, 4*e es aproximadamente 11. De tener ambas propiedades similares valores en el resto de las variables, tendríamos, a efectos del modelo, 2 observaciones similares excepto que en log_rooms una vale log(4) = 1.38, y la otra vale log(11) = 2.39.

Como el valor de log_rooms aumenta (aproximadamente) en 1, entonces la estimación del logaritmo del precio en la pripiedad con 11 habitaciones disminuiría respecto de la de 4 habitaciones en 0.048. Si llamamos A a la estimación del precio de la propiedad con 4 habitaciones y B a la estimación del precio de la propiedad con 11 habitaciones (con estimación del precio nos referimos a la exponencial de la estimación del logaritmo del precio), entonces tenemos que

Log(A) - Log(B) = 0.048 Log(A/B) = 0.048

Luego A/B = e^(0.048) => B = A * e^(-0.048), que es aproximadamente A * 0.95

De esto finalmente podemos concluir que si multiplicamos por e el número de habitaciones preservando el valor del resto de las variables, se espera entonces que el valor de la propiedad disminuya un 5%. Como ya comentamos anteriormente, este análisis es poco útil en la práctica ya que es poco realista que podamos aumentar tanto el número de las habitaciones preservando el resto de las variables fijas. Un análisis similar podemos hacer con el resto de las variables numéricas a las que les aplicamos logaritmo, aunque en esos casos, multiplicar el valor de cada variable por e tendrá un efecto multiplicativo positivo sobre el precio.

Respecto de la variable superficie_descubierta, el intercept de 0.0038 nos dice que, de ‘agregar’ un metro cuadrado de superficie sin cubrir, el logaritmo del precio aumentará, en promedio, 0.0038. Esto se traduce a que el precio se espera que se multiplique por e^(0.0038) = 1.003.

Respecto a las variables categóricas, tomemos por ejemplo barrios. La categoría basal en ese caso es precio_alto. El estimador del beta de la variable dummy de precio_bajo es -0.437587863. Esto significa que, de tener dos propiedades con similares características, solo que una es de un barrio de precios altos y la otra es de un barrio de precios bajos, entonces la diferencia en términos del logaritmo del precio de las dos propiedades será, en promedio 0.4376, siendo la propiedad del barrio más caro la que tiene el precio más elevado. De esto conluímos la proporción del precio de una propiedad de un barrio humilde respecto del precio de una propiedad con similares características proveniente de un barrio lujoso es, en promedio, e^(-0.4376) = 0.6455. En otras palabras, una propiedad de un barrio humilde cuesta un 35% menos, en promedio, que una propiedad proveniente de un barrio más rico.

Analicemos ahora el coeficiente de R² ajustado de este modelo y comparemos con los modelos anteriores.

models <- list(modelo_total = modelo_total,modelo_sin_l3 = modelo_sin_l3, modelo_barrios = modelo_barrios, modelo_descubierta = modelo_descubierta, modelo_log = modelo_log)
df_evaluacion_train = map_df(models, broom::glance, .id = "model") %>%
  # ordenamos por R2 ajustado
  arrange(desc(adj.r.squared))

df_evaluacion_train
NA

El R² ajustado del modelo con logaritmo es más alto incluso que el R² ajustado del modelo original donde usamos todas las variables y del modelo usando la superficie descubierta. De esto concluimos que este modelo explica mejor la variabilidad del precio que los modelos que construímos previamente. Cabe recordar, de todas formas, que este modelo en realidad explica la variación del logaritmo del precio, por ende, más que la variación del precio estaría explicando la variación proporcional del precio.

Analicemos ahora la validez de los supuestos del modelo analizando los residuos del mismo.

plot(modelo_log)

NA

Residuos vs valores predichos: Podemos encontrar poca estructura en los residuos. Se puede detectar que la variación de los residuos para valores predichos bajos (menores a 12) es más baja que para valores predichos ubicados en el intervalo [12,13]. En comparación al modelo de superficie descubierta, en este modelo estaría mucho más cerca de cumplirse el supuesto de homocedasticidad.

QQ plot: Los extramos inferior izquierdo y superior derecho no se ajustan a la distribución teórica. La normalidad tampoco se cumpliría por lo tanto. A comparación del modelo de superficie descubierta, la distribución está mucho más cerca de la distribución teórica.

Residuos estandarizados vs valores predichos: No se observa estructura en los datos.

Residuos vs Leverage: Hay 3 observaciones con un Leverage alto, aunque no están demasiado lejos del leverage de otras observaciones, así que podríamos desestimarlas.

Diagnóstico: El modelo de logaritmo presenta un poco de heterocedasticidad y tampoco satisface el supuesto de normalidad. Este modelo por lo tanto no satisface los supuestos del modelo lineal.

#Modelos finales

Creemos 2 modelos nuevos para hacer regresión lineal múltiple. Describamos nuestro primer modelo rápidamente

Modelo proportion: En este modelo en lugar de considerar superficie_descubierta o surface_covered tomaremos el porcentaje de superficie cubierta: 100 * Surface_covered/surface_total y surface_total para armar un modelo. Esto lo usamos debido a que a nuestro entender, cuanta mayor es la superficie de un terreno, máyor es la superficie descubierta. Corroboremos esa hipótesis mirando la correlación de spearman

cor(x = properties_train$surface_total,properties_train$superficie_descubierta, method = 'spearman')
[1] 0.4562264

La correlación de spearman es bastante mayor a 0. Si tomamos la proporción tenemos, sin embargo

cor(x = properties_train$surface_total, y = 100* properties_train$surface_covered/properties_train$surface_total, method = 'spearman')
[1] -0.2082784

La correlación es negativa y está más cerca del 0, por lo que es una correlación más débil.

Creemos entonces las dos variables que necesitamos para este modelo

properties_train <- properties_train %>%
  mutate(proportion_covered = 100*surface_covered/surface_total )

Procedamos así a realizar el modelo. Usaremos las mismas variables que el modelo de barrios solo que en vez de surface_covered, surperficie_descubierta usaremos surface_total y proportion_covered.

modelo_proportion<- lm(price ~  rooms + bathrooms + surface_total + proportion_covered + property_type + barrios, data = properties_train)
tidy_proportion <- tidy(modelo_proportion, conf.int = TRUE)
tidy_proportion
tidy(anova(modelo_proportion))

Los p-valores respectivos de cada estimador son todos muy pequeños y los intervalos de confianza están muy lejanos del 0. Los p-valores de la tabla de anova dan prácticamente 0, dándole todo esto validez a nuestro modelo.

Respecto a los estimadores de cada variable, destacamos que el estimador del beta de rooms da nuevamente negativo. Esto, nuevamente, se debe a que, por la colinealidad de las variables, al aumentar el número de habitaciones debería aumentar la superficie cubierta, y por ende, la proporción de superficie cubierta del tereno.

El estimador asociado a la variable proportion_covered es 1335. Esto significa que, de aumentar la proporción de superficie cubierta en un 1% del total de la superficie, el precio de una propierdad aumentará, en promedio, 1335 dolares.

Veamos rápidamente ahora si se cumplen los supuestos del modelo.

plot(modelo_proportion)

NA

Residuos vs valores predichos: Se puede ver una estructura en los residuos. La varianza de los mismos aumenta cuanto mayores son los valores predichos.

QQ plot: Los extremos no coinciden con la distribución teórica

Residuos vs Leverage: Hay una observación con un Leverage muy alto (mayor a 0.0030). Hay otra observación con un Leverage bastante alto.

Diagnóstico: No se satisfacen los supuestos de homocedasticidad ni el de normalidad, y hay una observación con un leverage muy superior al resto. Por lo que concluimos que no se satisfacen los supuestos del modelo lineal.

Antes de medir el R² ajustado, creemos nuestro último modelo. Este modelo usará otra vez el logaritmo natural, pero le sumará 1 a las variables para así poder aplicar el logaritmo incluso cuando las variables alcancen el valor 0. Definamos el logaritmo corrido que usaremos para tranformar las variables numéricas.

logc <- function(x){return (log(x+1))}

Las variables que vamos a transformar son price, rooms, bathrooms y surface_total. Las variables categóricas barrios y property_type y la variable proportion_covered serán utilizadas con sus valores originales.

properties_train <- properties_train %>%
  mutate(logc_price = logc(price), logc_rooms = logc(rooms), logc_bathrooms = logc(bathrooms),  logc_surface_total
         =logc(surface_total))

Procedamos entonces a entrenar el modelo y observemos los resultados obtenidos.

modelo_logc<- lm(logc_price ~  logc_rooms + logc_bathrooms + logc_surface_total + proportion_covered + property_type + barrios, data = properties_train)
tidy_logc <- tidy(modelo_logc, conf.int = TRUE)
tidy_logc
tidy(anova(modelo_logc))

Los p-valores respectivos a cada variable del modelo son significativamente pequelos y los intervalos de confianza no contienen al 0. Los p-valores de la tabla de anova dan prácticamente 0, por lo que todas las variables son significativas para el modelo.

Respecto a los estimadores, la única variable numérica que dio negativo el estimador es, nuevamente, la variable relacionada con rooms, en este caso logc_rooms.

Veamos ahora si se satisfacen los supuestos del modelo

plot(modelo_logc)

Diagnóstico: Para ahorrar explicación, los gráficos mostrados son muy similares a los gráficos del otro modelo del logaritmo que armamos. Por ende, los residuos presentan una ligera heterocedasticidad y no cumplen el supuesto de normalidad. Hay algunas observaciones con bastante Leverage pero el valor de los Leverages no es demasiado elevado de todos modos. Por lo tanto, el modelo con el logaritmo corrido no cumple los supuestos del modelo lineal.

Otro problema de este modelo es la interpretabilidad de sus resultados. Con el logaritmo ya de por sí no es fácil hacer una interpretación sencilla, pero al correr el logaritmo hay que tener en cuenta el corrimiento a la hora de interpretar.

Por ejemplo, en logc_bathrooms el estimador de la pendiente del logaritmo corrido del precio es 0.28314352. Esto significa que, de aumentar en 1 en valor del logaritmo corrido de bathrooms, entonces el logaritmo del precio aumentará, en promedio, 0.283. Para que aumente en 1 el logaritmo corrido, necesitamos que el valor de la variable corrida se multiplique por e. Recordemos que 11 era, aproximadamente, 4 * e. Luego, si tenemos una propiedad con 3 baños y otra con 10, tenemos que (3+1) * e es aproximadamente 10+1. Por lo tanto, log ( 10 + 1) = log(3 + 1) + 1. Esto nos dice que, si tenemos dos propiedades similares, solo que una tiene 10 baños y la otra con 3, entonces el logaritmo corrido del precio de la propiedad con 10 baños aumentará, respecto del logaritmo corrido del precio de la propiedad con 3 baños, en promedio, en 0.2831.

Llamemos A al precio estimado de la propiedad de 10 baños y B al precio estimado de la propiedad con 3 baños, tenemos entonces que log(A+1) = log(B+1) + 0.2831 Luego (A+1)/(B+1) = e^(0.2831) = 1.32

Despejando, A = 1.32 * B + 0.32

Es decir, esperamos que el precio de la propiedad con 10 habitaciones sea el precio de la propiedad con3 habitaciones multiplicado por 1.32 más 0.32 (que es despreciable).

El proceso para llegar a esta conclusión fue una cuenta muy tediosa y tuvimos que fijar un ejemplo particular para hacer todo más comprensible, por lo que la interpretación de este modelo es más complicada incluso que la interpretación del primer modelo logarítmico.

Vamos a buscar el mejor modelo. Para eso primero seleccionaremos 4 modelos: los dos modelos que recién creamos, y dos modelos de los que teníamos previamente. Uno de esos modelos será el modelo original de logaritmo, que nos había dado el R² ajustado más grande, y por ende es el modelo que mejor exlpica la variabilidad entre los modelos originales (aunque recordemos que ese R² no se corresponde específicamente a la variabilidad del precio). El otro modelo será el modelo de superficie descubierta, que, si bien posee un coeficiente de R² ajustado más chico que el del logaritmo y que el modelo donde consideramos todas las variables, es un modelo mucho más simple y fácil de interpretar que estos 2 últimos.

Comparemos la performance de cada uno de los 4 modelos seleccionados en términos de la proporción de variabilidad explicada. Para eso, comparamos los coeficientes de R² ajustado.

models <- list(modelo_descubierta = modelo_descubierta, modelo_log = modelo_log, modelo_proportion = modelo_proportion, modelo_logc = modelo_logc)
df_evaluacion_train = map_df(models, broom::glance, .id = "model") %>%
  # ordenamos por R2 ajustado
  arrange(desc(adj.r.squared))

df_evaluacion_train
NA

El modelo_logc tiene un coeficiente de R² ajustado un poco mayor que el modelo_log

#Evaluación de los modelos en el dataset de training

Vamos ahora a proceder a evaluar los 4 modelos que seleccionamos para ver cual es el mejor. Para hacer la evaluación vamos a utilizar el error cuadrático medio, el RMSE. El modelo lineal busca estimar los coeficientes para minimizar el error cuadrático medio, por ende tendría sentido utilizar esta métrica para evaluar la performance de los modelos. De todos modos, esta métrica tendría más sentido en el conjunto de testing, ya que podemos minimizar el valor de esta métrica simplemente haciendo más complejo al modelo y agregando la mayor cantidad de variables posibles al mismo.

Un inconveniente de esta métrica es que no es muy robusta ante la presencia de outliers. De tener un dataset con un gran número de outliers deberíamos haber hecho un preprocesamiento para limpiar los datos y así poder realizar modelos lineales que no se vean muy afectados por las observaciones con alto leverage que resultan outliers.

Otro inconveniente que tenemos es que para hacer la validación todos los modelos lineales deberían predecir el precio. Sin embargo los modelos con logaritmo predicen una composición del precio con una función logaritmica. La función augment nos da los valores predichos y los residuos. Para comparar la performace de los modelos convendría, en los modelos logaritmicos, transformar los valores predichos para que predigan el precio, y actualizar los residuos con esa información.

Comencemos con el primer modelo logarítmico que armamos. En ese modelo explicamos la variación de log(price) respecto a las otras variables. Para obtener una predicción del precio podemos componer con la función inversa del logaritmo: la función exponencial. Una vez hecha esa conversión, tenemos también que actualizar el residuo, que resulta ser la diferencia entre el valor real, el precio, y el nuevo valor ajustado.

pred_log <- augment(modelo_log, newdata=properties_train) %>%
  mutate(.fitted = exp(.fitted), .resid = price-.fitted) %>%
  select(id, price, .fitted, .resid)
pred_log

Veamos el error cuadrático medio del modelo

rmse(data = pred_log, truth = price, estimate = .fitted)
NA

Hagamos lo mismo con el modelo de superficie descubierta

pred_descubierta <- augment(modelo_descubierta, newdata=properties_train) %>%
  select(id, price, .fitted, .resid)
pred_descubierta

Calculemos su RMSE

rmse(data = pred_descubierta, truth = price, estimate = .fitted)
NA

El RMSE dio más alto, veamos con el modelo donde incluímos el porcentaje de superficie cubierta.

pred_proportion <- augment(modelo_proportion, newdata=properties_train) %>%
  select(id, price, .fitted, .resid)
pred_proportion

Analicemos el RMSE.

rmse(data = pred_proportion, truth = price, estimate = .fitted)

Por último probemos con el último modelo logarítmico que creamos. En este caso compusimos price con la función ln(x+1). La inversa de esa función es g(x) = exp(x)-1. Basándonos en eso actualizamos los valores predichos y los residuos.

pred_logc <- augment(modelo_logc, newdata=properties_train) %>%
  mutate(.fitted = exp(.fitted) - 1, .resid = price-.fitted) %>%
  select(id, price, .fitted, .resid)
pred_logc

Veamos el RMSE de este modelo logarítmico.

rmse(data = pred_logc, truth = price, estimate = .fitted)

El RMSE del modelo logarítmico corrido dio más bajo que en los otros modelos, con un error cuadrático medio de 68384.41. Recordemos que este modelo es más dificil de interpretar que los modelos que no involucran logaritmo. Sin embargo, el único modelo con un error cuadrático medio cercano también es un modelo logarítmico, así que consideramos, en términos del dataset de entrenamiento, que el modelo de logaritmo corrido es el mejor modelo de los 4 que seleccionamos. Ahora analicemos como dan las métricas en el dataset de testing.

#Evaluación de los modelos en el dataset de testing

Para poder validar en testing deberíamos redefinir todas las variables que usamos en ese modelo.

Para traer de nuevo la variable ‘barrios’, vamos a extraer la información del dataset de entrenamiento el lugar de repetir el procedimiento en el dataset de exploración, de esa forma evitamos usar el precio de las propiedades de testing para definir la variable barrios y además somos consistentes a la elección que hicimos antes.

barrio_seleccion <- unique(properties_train %>%
                             select(l3, barrios))
                             
barrio_seleccion

Hacemos un left join para anexar la variable barrios al dataset de testing.

properties_test <-properties_test %>%
  left_join(., barrio_seleccion, by = "l3")
head(properties_test)
properties_test <-properties_test %>%
  mutate(superficie_descubierta = surface_total-surface_covered, proportion_covered = 100*surface_covered/surface_total, log_price = log(price), log_rooms = log(rooms), log_bathrooms = log(bathrooms), log_surface_covered = log(surface_covered), logc_price = logc(price), logc_rooms = logc(rooms), logc_bathrooms = logc(bathrooms), logc_surface_total = logc(surface_total))

Evaluemos ahora sí cada modelo, y encontremos el RMSE de cada uno

Primer modelo logarítmico

pred_log_test <- augment(modelo_log, newdata=properties_test) %>%
  mutate(.fitted = exp(.fitted), .resid = price-.fitted)

rmse(pred_log_test, truth = price, estimate = .fitted)
NA

Modelo de superficie descubierta

pred_descubierta_test <- augment(modelo_descubierta, newdata=properties_test)

rmse(pred_descubierta_test, truth = price, estimate = .fitted)
NA

Modelo de con porcentaje de superficie cubierta

pred_proportion_test <- augment(modelo_proportion, newdata=properties_test)

rmse(pred_proportion_test, truth = price, estimate = .fitted)
NA

Modelo logarítmico con corrimiento

pred_logc_test <- augment(modelo_logc, newdata=properties_test) %>%
  mutate(.fitted = exp(.fitted)-1, .resid = price-.fitted)

rmse(pred_logc_test, truth = price, estimate = .fitted)
NA

Nuevamente, el modelo con error cuadrático medio más chico es el modelo con logaritmo corrido, seguido por el primer modelo logarítmico.

Dado que este modelo es el mejor tanto en el conjunto de entrenamiento como en el de testing, y, además, el único modelo medianamente cercano en este sentido es el otro modelo modelo dificil de interpretar, concluimos que el modelo con logaritmo corrido es el mejor modelo con mejor performance y el más adecuado para explicar la variación del precio.

LS0tCnRpdGxlOiAiVHJhYmFqbyBQcsOhY3RpY28gMiBFRUEiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KRW4gZXN0ZSB0cmFiYWpvICB2YW1vcyBhIGFuYWxpemFyIHVuIGNvbmp1bnRvIGRlIGRhdG9zIGNvbiBpbmZvcm1hY2nDs24gYXNvY2lhZGEgYSBwcm9waWVkYWRlcyBlbiB2ZW50YS4gTnVlc3RybyBvYmpldGl2byBzZXLDoSBleHBsaWNhciBlbCBwcmVjaW8gZGUgdmVudGEgZGUgZGljaGFzIHByb3BpZWRhZGVzIHkgcGFyYSB0YWwgZmluIHJlYWxpemFyZW1vcyB1biBtb2RlbG8gbGluZWFsIG3Dumx0aXBsZS4KCgojIyBQcmVwYXJhY2nDs24gZGUgbG9zIERhdG9zCgpDb21lbnphbW9zIGNhcmdhbmRvIGxhcyBsaWJyZXLDrWFzIFRpZHl2ZXJzZSB5IEx1YnJpZGF0ZQoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKYGBgCkNhcmdhbW9zIGxvcyBkYXRhc2V0cyBkZSB0cmFpbiB5IHRlc3QuIExlIGRhbW9zIHVuIHZpc3Rhem8gYWwgZGF0YXNldCBkZSB0cmFpbi4KICAKYGBge3J9CnByb3BlcnRpZXNfdHJhaW4gPC0gcmVhZC5jc3YoJ2FyX3Byb3BlcnRpZXNfdHJhaW4uY3N2JykKcHJvcGVydGllc190ZXN0IDwtIHJlYWQuY3N2KCdhcl9wcm9wZXJ0aWVzX3Rlc3QuY3N2JykKZ2xpbXBzZShwcm9wZXJ0aWVzX3RyYWluKQoKYGBgCgpIYWdhbW9zIHVuYSBtZWpvciB2aXN1YWxpemFjacOzbiBkZSBsb3MgZGF0b3MgZGUgZW50cmVuYW1pZW50byB1c2FuZG8gbGEgbGlicmVyw61hIGtuaXRyLgoKYGBge3J9CmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKCnByb3BlcnRpZXNfdHJhaW4gJT4lICAKICBoZWFkKCkgJT4lIAogIGthYmxlKCkgJT4lIAogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIikpCmBgYAoKVmVhbW9zIGVsIHRpcG8gZGUgY2FkYSB2YXJpYWJsZSB1c2FuZG8gc3VtbWFyeS4KCmBgYHtyfQoKc3VtbWFyeShwcm9wZXJ0aWVzX3RyYWluKSAgCgpgYGAKRWwgZGF0YXNldCBkZSBlbnRyZW5hbWllbnRvIHRpZW5lIDggdmFyaWFibGVzLiBWYW1vcyBhIGRlc2NyaWJpcmxhcyB1bmEgcG9yIHVuYQoKaWQgZXMgdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhIHF1ZSBpZGVudGlmaWNhIGEgY2FkYSB1c3VhcmlvLCB5IHBvciBlbmRlLCBubyB0aWVuZSB2YWxvcmVzIHJlcGV0aWRvcy4gbDMgRXMgdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhIHF1ZSByZXByZXNlbnRhIGVsIGJhcnJpbyBkZSBsYSBwcm9waWVkYWQuIEEgc2ltcGxlIHZpc3RhIHBvZGVtb3MgZGVkdWNpciBxdWUgcG9zZWUgYWwgbWVub3MgMTAgdmFsb3JlcyBkaXN0aW50b3MsIHNpZW5kbyBQYWxlcm1vLCBCZWxncmFubywgQWxtYWdybywgQ2FiYWxsaXRvLCBWaWxsYSBDcmVzcG8geSBSZWNvbGV0YSBsb3MgbcOhcyBmcmVjdWVudGVzLiByb29tcyBlcyB1bmEgdmFyaWFibGUgbnVtw6lyaWNhIHF1ZSB0b21hIHZhbG9yZXMgZW50ZXJvcyBkZWwgMSBhbCA4IHkgcmVwcmVzZW50YSBsYSBjYW50aWRhZCBkZSBoYWJpdGFjaW9uZXMgZGUgbGEgcHJvcGllZGFkLiBCYXRocm9vbXMgdG9tYSB2YWxvcmVzIGRlbCAxIGFsIDUgeSByZXByZXNlbnRhIGxhIGNhbnRpZGFkIGRlIGJhw7Fvcy4gc3VyZmFjZV90b3RhbCB5IHN1cmZhY2VfY292ZXJlZCBtaWRlbiBsYSBzdXBlcmZpY2llIHRvdGFsIHkgbGEgc3VwZXJmaWNpZSBjdWJpZXJ0YSByZXNwZWN0aXZhbWVudGUsIHF1ZSBhc3VtaW1vcyBxdWUgZXN0w6FuIG1lZGlkYXMgZW4gbWV0cm9zIGN1YWRyYWRvcy4gUG9zaWJsZW1lbnRlIGVzdGFzIHZhcmlhYmxlcyB0b21lbiB2YWxvcmVzIGVudGVyb3MsIGF1bnF1ZSBubyBzZXLDrWEgbmVjZXNhcmlvIHF1ZSBsbyBoYWdhbi4gcHJpY2UgZGV0ZXJtaW5hIGVsIHByZWNpbyBlbiBkb2xhcmVzLCB5IHByb3BlcnR5X3R5cGUgZXMgdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhIHF1ZSB0b21hIDMgdmFsb3JlcyBkaXN0aW50b3M6IENhc2EsIFBIIHkgRGVwYXJ0YW1lbnRvIChlbCBtw6FzIGZyZWN1ZW50ZSkuCgpQYXJhIGNvbmNsdWlyIGVsIGFuw6FsaXNpcyBkZSBudWVzdHJvcyBkYXRvcywgdmVhbW9zIGxhIGNvcnJlbGFjacOzbiBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMsIHBhcmEgY29tZW56YXIgYSB2ZXIgcmVsYWNpb25lcyBxdWUgYXBhcmV6Y2FuIGVudHJlIGxhcyB2YXJpYWJsZXMuIFZhbW9zIGEgdXNhciBsYSBjb3JyZWxhY2nDs24gZGUgU3BlYXJtYW4gZGFkbyBxdWUgbm8gZXRyYWppbW9zIG91dGxpZXJzIGRlIGxvcyBkYXRvcy4gCgpgYGB7cn0KZGF0b3NfdHJhaW5fbnVtZXJpY29zIDwtIHByb3BlcnRpZXNfdHJhaW4gJT4lCiAgc2VsZWN0KHJvb21zLGJhdGhyb29tcywgc3VyZmFjZV90b3RhbCwgc3VyZmFjZV9jb3ZlcmVkLCBwcmljZSkKCmNvcihkYXRvc190cmFpbl9udW1lcmljb3MsIG1ldGhvZCA9ICdzcGVhcm1hbicpICAKYGBgClBvZGVtb3MgZW5jb250cmFyIHVuYSBjb3JyZWxhY2nDs24gcG9zaXRpdmEgZW50cmUgdG9kYXMgbGFzIHZhcmlhYmxlcyB5YSBxdWUgZXN0w6FuIGF0YWRhcyBhbCB0YW1hw7FvIGRlIGxhIHByb3BpZWRhZDogQ3VhbnRvIG3DoXMgZ3JhbmRlIHNlYSB1bmEgcHJvcGllZGFkLCBtYXlvciBzZXLDoSBsYSBjYW50aWRhZCBkZSBoYWJpdGFjaW9uZXMsIGJhw7Fvcywgc3VwZXJmaWNpZSB5IGVsIHByZWNpbyBkZSBsYSBtaXNtYS4gCkRlc3RhY2Ftb3MgbGEgZnVlcnRlIGNvcnJlbGFjacOzbiBxdWUgaGF5IGVudHJlIGFtYmFzIHZhcmlhYmxlcyBkZSBzdXBlcmZpY2llICgwLjk1OSkuIEVzdGFzIGRvcyB2YXJpYWJsZXMgdGFtYmnDqW4gcHJlc2VudGFuIHVuYSBjb3JyZWxhY2nDs24gYmFzdGFudGUgYWx0YSBjb24gbGEgY2FudGlkYWQgZGUgaGFiaXRhY2lvbmVzICgwLjgyOSBwYXJhIHN1cmZhY2VfdG90YWwgeSAwLjg2NCBwYXJhIHN1cmZhY2VfY292ZXJlZCkuIExhcyB2YXJpYWJsZXMgZGUgc3VwZXJmaWNpZSB0YW1iacOpbiBzZSBjb3JyZWxhY2lvbmFuIGJhc3RhbnRlIGNvbiBlbCBwcmVjaW8uIFRvZG8gZXN0byBzZXLDoSB0ZW5pZG8gZW4gY3VlbnRhIG3DoXMgYWRlbGFudGUgYSBsYSBob3JhIGRlIHJlYWxpemFyIGxvcyBtb2RlbG9zIGxpbmVhbGVzLgoKIyBQcmltZXJvcyBtb2RlbG9zIGRlIFJlZ3Jlc2nDs24gbGluZWFsIG3Dumx0aXBsZQoKUmVhbGljZW1vcyB1biBtb2RlbG8gaW5pY2lhbCB1c2FuZG8gdG9kYXMgbGFzIHZhcmlhYmxlcyBwYXJhIHByZWRlY2lyIGVsIHByZWNpbwoKYGBge3J9CmxpYnJhcnkodGlkeW1vZGVscykKCm1vZGVsb190b3RhbCA8LSBsbShwcmljZSB+IGwzICsgcm9vbXMgKyBiYXRocm9vbXMgKyBzdXJmYWNlX3RvdGFsICsgc3VyZmFjZV9jb3ZlcmVkICsgcHJvcGVydHlfdHlwZSwgZGF0YSA9IHByb3BlcnRpZXNfdHJhaW4pCnRpZHlfdG90YWwgPC0gdGlkeShtb2RlbG9fdG90YWwsIGNvbmYuaW50ID0gVFJVRSkKdGlkeV90b3RhbApgYGAKCkFuYWxpY2Vtb3MgbGFzIHZhcmlhYmxlcyBkdW1taWVzLiBFbXBlY2Vtb3MgdmllbmRvIGxvcyB2YWxvcmVzIHF1ZSB0b21hIGxhIHZhcmlhYmxlIGwzCgpgYGB7cn0KdW5pcXVlKHByb3BlcnRpZXNfdHJhaW4gJT4lCiAgICAgICAgIHNlbGVjdChsMykpCmBgYApMb3MgYmFycmlvcyBlbiBlbCBtb2RlbG8gZXN0YXLDrWFuIG9yZGVuYWRvcyBhbGZhYsOpdGljYW1lbnRlLiBTZSBwdWVkZSBvYnNlcnZhciBxdWUgbm8gaGF5IHVuYSBkdW1taWUgY29uIGVsIGJhcnJpbyBkZWwgQWJhc3RvLCBxdWUgc2Vyw61hIGVsIHByaW1lciBiYXJyaW8gZW4gb3JkZW4gYWxmYWLDqXRpY28uIEVzdG8gcXVpZXJlIGRlY2lyIHF1ZSBBYmFzdG8gZXMgbGEgY2F0ZWdvcsOtYSBiYXNhbCwgZW4gb3RyYXMgcGFsYWJyYXMsIGRlIHZhbGVyIDAgdG9kYXMgbGFzIHZhcmlhYmxlcyBkdW1taWVzIHJlbGFjaW9uYWRhcyBhIGxhIGxvY2FsaWRhZCwgZXN0YXLDrWFtb3MgdmllbmRvIGVsIHByZWNpbyBlc3BlcmFkbyBkZSB1bmEgcHJvcGllZGFkIGRlbCBBYmFzdG8gKGVuIGZ1bmNpw7NuIGRlIGxvcyB2YWxvcmVzIGRlIGxhcyB2YXJpYWJsZXMgcXVlIG5vIGludm9sdWNyYW4gYSBsYSBsb2NhbGl6YWNpw7NuKS4gRWwgY29lZmljaWVudGUgQmV0YSBlc3RpbWFkbyByZXNwZWN0aXZvIGRlIGNhZGEgcHJvcGllZGFkIG5vcyBtdWVzdHJhIGVudG9uY2VzIGxhIGRpZmVyZW5jaWEgZW4gcHJlY2lvIHByb21lZGlvIGFsIGNvbXByYXIgdW5hIHByb3BpZWRhZCBlbiBlc2EgbG9jYWxpZGFkIHkgZGUgY29tcHJhcmxhIGVuIGVsIEFiYXN0byAoZXMgZGVjaXIsIHRvZGFzIGxhcyBvdHJhcyB2YXJpYWJsZXMgc2UgbWFudGVuZ2FuIGZpamFzKS4gQXPDrSwgc2kgdHV2aWVzZW1vcyBkb3MgcHJvcGllZGFkZXMgc2ltaWxhcmVzLCB1bmEgZW4gYWdyb25vbcOtYSwgeSBvdHJhIGVuIGVsIEFiYXN0bywgbGEgcHJpcGllZGFkIGRlIEFncm9ub21pYSB2YWxkcsOtYSwgZW4gcHJvbWVkaW8sIDEyNDEgZG9sYXJlcyBtw6FzIHF1ZSBsYSBwcm9waWVkYWQgZGVsIEFiYXN0by4KClJlc3BlY3RvIGEgbGEgc2lnbmlmaWNhdGl2aWRhZCBkZSBsb3MgY29lZmljaWVudGVzLCBWZWFtb3MgZWwgcC12YWxvciB5IGxvcyBpbnRlcnZhbG9zIGRlIGNvbmZpYW56YS4gCgoKYGBge3J9CnRpZHlfdG90YWwgJT4lCiAgc2VsZWN0KHRlcm0sIHAudmFsdWUsIGNvbmYubG93LCBjb25mLmhpZ2gpCmBgYAoKU2UgcHVlZGUgb2JzZWVydmFyIHF1ZSBhcHJveGltYWRhbWVudGUgbGEgbWl0YWQgZGUgbGFzIHZhcmlhYmxlcyBkdW1taWVzIGdlbmVyYWRhcyBwb3IgbGEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgbDMgbm8gc29uIHNpZ25pZmljYXRpdmFzLCB5YSBxdWUgc3UgcC12YWxvciBlcyBtYXlvciBhIDAuMDUgeSBsb3MgaW50ZXJ2YWxvcyBkZSBjb25maWFuemEgY29udGllbmVuIGFsIDAuIFBvciBlamVtcGxvLCBsYSB2YXJpYWJsZSBsM0Fncm9ub23DrWEgdGllbmUgdW4gcC12YWxvciBkZSAwLjkwNyB5IHN1IGludGVydmFsbyBkZSBjb25maWFuemEgY29udGllbmUgYWwgY2Vyby4gUG9yIGVuZGUsIG5vIHRlbmVtb3MgZXZpZGVuY2lhIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdmEgZGUgcXVlIHVuYSBwcm9waWVkYWQgZGUgQmFsdmFuZXJhIHRlbmdhIHVuIHByZWNpbyBlc3BlcmFkbyBkaWZlcmVudGUgYWwgZGUgdW5hIHByb3BpZWRhZCBkZWwgQWJhc3RvLiAKCkVzdG8gbm9zIGRhIHVuIGluZGljaW8gZGUgcXVlIHBvc2libGVtZW50ZSBsYSB2YXJpYWJsZSBjYXRlZ8OzcmljYSBsMyBubyBzZWEgYmVuZWZpY2lvc2EgcGFyYSBlbCBtb2RlbG8sIHBvciBsbyBxdWUgc2Vyw61hIGFjb25zZWphYmxlIHF1aXRhcmxhIG8gdHJhbnNmb3JtYXJsYSBkZSBhbGfDum4gbW9kby4gRXN0byBsbyB2ZXJlbW9zIG3DoXMgZW4gcHJvZnVuZGlkYWQgY3VhbmRvIHZlYW1vcyBlbCB0ZXN0IEYuIFJlc3BlY3RvIGEgbGFzIG90cmFzIHZhcmlhYmxlcyBkZWwgZGF0YXNldCBkZSBlbnRyZW5hbWllbnRvLCBsb3MgcC12YWxvcmVzIHNvbiBleHRyZW1hZGFtZW50ZSBwZXF1ZcOxb3MgeSBzdXMgaW50ZXJ2YWxvcyBkZSBjb25maWFuemEgZXN0w6FuIG11eSBhbGVqYWRvcyBkZWwgMCwgYXPDrSBxdWUgbnVlc3RyYSBjb25jbHVzacOzbiBlcyBxdWUgbGEgaW5jbHVzacOzbiBkZSBlc2FzIHZhcmlhYmxlcyBzw60gZXMgc2lnbmlmaWNhdGl2YSBlbiBlbCBtb2RlbG8uCgpMYSBvdHJhIHZhcmlhYmxlIGNhdGVnw7NyaWNhLCBwcm9wZXJ0eV90eXBlLCB0aWVuZSAzIHZhbG9yZXMgcG9zaWJsZXM6IENhc2EsIERlcGFydGFtZW50byB5IFBILiBDb21vIG5vIGhheSB1bmEgRHVtbXkgY29uIENhc2EsIGVudG9uY2VzIHNpIGxhcyB2YXJpYWJsZXMgcHJvcGVydHlfdHlwZURlcGFydGFtZW50byB5IHByb3BlcnR5X3R5cGVQSCB2YWxpZXNlbiAwLCBlc3RhcsOtYW1vcyB2aWVuZG8gZWwgcHJlY2lvIHByb21lZGlvIGRlIHVuYSBjYXNhIGVuIGZ1bmNpw7NuIGRlIGxhcyB2YWxvcmVzIHF1ZSB0b21hbiBsYXMgdmFyaWFibGVzIGRlbCBtb2RlbG8gcXVlIG5vIGludm9sdWNyYW4gYSBsYSBwcm9waWVkYWQuIExvcyBjb2VmaWNpZW50ZXMgQmV0YSBlc3RpbWFkb3MgZGUgcHJvcGVydHlfdHlwZURlcGFydGFtZW50byB5IHByb3BlcnR5X3R5cGVQSCBtdWVzdHJhbiBsYSBkaWZlcmVuY2lhIGVzcGVyYWRhIGRlbCBwcmVjaW8gZGUgdW4gZGVwYXJ0YW1lbnRvIHkgZGUgdW4gUEggcmVzcGVjdG8gZGVsIHByZWNpbyBkZSB1bmEgY2FzYSBjb24gc2ltaWxhcmVzIGNhcmFjdGVyw61zdGljYXMuIEFzw60gcG9yIGVqZW1wbG8sICBzaSB0dXZpZXNlbW9zIHVuIGRlcGFydGFtZW50byB5IHVuYSBjYXNhIHViaWNhZG9zIGVuIGxhIG1pc21hIGxvY2FsaWRhZCwgY29uIHNpbWlsYXIgY2FudGlkYWQgZGUgaGFiaXRhY2lvbmVzLCBiYcOxb3MsIHkgcXVlIG9jdXBhbiB5IGN1YnJhbiBzdXBlcmZpY2llcyBzaW1pbGFyZXMsIGVudG9uY2VzIGVsIGRlcGFydGFtZW50byB2YWxkcsOtYSwgZW4gcHJvbWVkaW8sIDkxNDg1IGRvbGFyZXMgbcOhcyBkZSBsbyBxdWUgdmFsZHLDrWEgbGEgY2FzYS4gCgpIYXkgcXVlIHRlbmVyIGVuIGN1ZW50YSBxdWUsIGRlIHRvZG9zIG1vZG9zLCBsb3MgZGVwYXJ0YW1lbnRvcyBlbiBnZW5lcmFsIG5vIHNvbiB0YW4gZ3JhbmRlcyBjb21vIHVuYSBjYXNhLCBhc8OtIHF1ZSBlcyBkaWZpY2lsIHF1ZSBlc3TDqW4gZW4gaWd1YWxkYWQgZGUgY29uZGljaW9uZXMuIEFkZW3DoXMsIGNvbW8gZXN0ZSBtb2RlbG8gbm8gcHJlc2VudGEgaW50ZXJhY2Npw7NuIGVudHJlIHZhcmlhYmxlcywgbm8gcG9kZW1vcyBjb21wYXJhciBlbCBlZmVjdG8sIHBvciBlamVtcGxvLCBkZSBhdW1lbnRhciBlbCBuw7ptZXJvIGRlIGhhYml0YWNpb25lcyBlbiBlbCBwcmVjaW8gZGUgdW4gZGVwYXJ0YW1lbnRvIHkgZW4gZWwgcHJlY2lvIGRlIHVuYSBjYXNhLCB5YSBxdWUgYWwgbm8gaGFiZXIgaW50ZXJhY2Npw7NuLCBudWVzdHJvIG1vZGVsbyBpbnRlcnByZXRhcsOzYSBxdWUgbGEgcGVuZGllbnRlIGRlbCBwcmVjaW8gcmVzcGVjdG8gZGUgbGEgdmFyaWFibGUgcm9vbXMgbm8gZGVwZW5kZSBkZWwgdGlwbyBkZSBwcm9waWVkYWQuCgpSZXNwZWN0byBhIGxvcyBvdHJvcyBjb2VmaWNpZW50ZXMsIHNlIHB1ZWRlIGVzcGVyYXIgcXVlIGEgY3VhbnRvIG1heW9yIHNlYSBlbCBuw7ptZXJvIGRlIGJhw7FvcywgaGFiaXRhY2lvbmVzIG8gc3VwZXJmaWNpZSBxdWUgb2N1cGEgbyBxdWUgY3VicmUgbGEgcHJvcGllZGFkLCBtw6FzIGFsdG8gc2Vyw6EgZWwgcHJlY2lvIGRlIGxhIG1pc21hLiBFbCBtb2RlbG8gc2luIGVtYmFyZ28gbm9zIGRpY2UgcXVlIGVsIGVzdGltYWRvciBkZSBsYSBwZW5kaWVudGUgcmVzcGVjdG8gZGUgbGEgdmFyaWFibGUgcm9vbXMgZXMgLTQzNjAuIEVzdG8gc2lnbmlmaWNhIHF1ZSBzaSBkb3MgcHJvcGllZGFkZXMgc29uIGlkw6ludGljYXMsIHNhbHZvIHF1ZSB1bmEgdGllbmUgdW5hIGhhYml0YWNpw7NuIG3DoXMgcXVlIGxhIG90cmEsIGVudG9uY2VzIHNlIGVzcGVyYSBxdWUgbGEgcHJvcGllZGFkIGNvbiB1bmEgaGFiaXRhY2nDs24gbcOhcyBjdWVzdGUgNDM2MCBkw7NsYXJlcyBtZW5vcy4gRXN0ZSBleHRyYcOxbyByZXN1bHRhZG8gcHVlZGUgZGViZXJzZSBhIHF1ZSBsYSB2YXJpYWJsZSByb29tcyBlc3TDqSBtdXkgY29ycmVsYWNpb25hZGEgY29uIGxhcyB2YXJpYWJsZXMgZGUgc3VwZXJmaWNpZSwgbG8gcXVlIGhhY2UgcXVlIGVsIG1vZGVsbyBpbnRlcnByZXRlIGVycsOzbmVhbWVudGUgcXVlIGF1bWVudGFyIGVsIG7Dum1lcm8gZGUgaGFiaXRhY2lvbmVzIHJlcGVyY3V0ZSBuZWdhdGl2YW1lbnRlIGVuIGVsIHByZWNpbyBkZSB1bmEgcHJvcGllZGFkLiBMbyBxdWUgc3VjZWRlIGVuIHJlYWxpZGFkIGVzIHF1ZSBubyBlcyByZWFsaXN0aWNhbWVudGUgcG9zaWJsZSBhdW1lbnRhciBlbCBuw7ptZXJvIGRlIGhhYml0YWNpb25lcyBtYW50ZW5pZW5kbyBsYXMgb3RyYXMgdmFyaWFibGVzIGZpamFzLCBkZWJpZG8gYSBsYSBjb3JyZWxhY2nDs24gcG9zaXRpdmEgcXVlIGhheSBjb24gbGFzIG90cmFzIHZhcmlhYmxlcyBkZWwgZGF0YXNldCwgcHJpbmNpcGFsbWVudGUgbGFzIHZhcmlhYmxlcyBkZSBzdXBlcmZpY2llLgoKQW5hbGljZW1vcyBhaG9yYSBlbCBjb2VmaWNpZW50ZSBGLgoKYGBge3J9CnRpZHkoYW5vdmEobW9kZWxvX3RvdGFsKSkKYGBgCgpMYSB0YWJsYSBkZSBhbm92YSBub3MgbXVlc3RyYSBxdWUsIHNlZ8O6biBlbCByZXN1bHRhZG8gZGVsICB0ZXN0IEYsIHRvZGFzIGxhcyB2YXJpYWJsZXMgc29uIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdmFzLCB5YSBxdWUgdG9kb3MgbG9zIHAtdmFsb3JlcyBkYW4gcHLDoWN0aWNhbWVudGUgMC4KClNpIGJpZW4gb2J0dXZpbW9zIHF1ZSBsYSB2YXJpYWJsZSBsMyBlcyBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhLCBzb3NwZWNoYW1vcyBxdWUgZXN0YSB2YXJpYWJsZSBwdWVkZSByZXBlcmN1dGlyIG5lZ2F0aXZhbWVudGUgZW4gZWwgbW9kZWxvLCBzZSBwdWVkZSB2ZXIsIHBvciBlamVtcGxvLCBxdWUgbG9zIGVycm9yZXMgZXN0w6FuZGFyZXMgZGUgZXN0YXMgdmFyaWFibGVzIHNvbiBjb25zaWRlcmFibGVtZW50ZSBhbHRvcy4gVmFtb3MgYSByZWFsaXphciBvdHJvIG1vZGVsbyBzaW4gbGEgdmFyaWFibGUgbDMgeSBjb21wYXJlbW9zIGxvcyByZXN1bHRhZG9zIG9idGVuaWRvcyBlbiBhbWJhcyByZWdyZXNpb25lcy4KCmBgYHtyfQptb2RlbG9fc2luX2wzIDwtIGxtKHByaWNlIH4gIHJvb21zICsgYmF0aHJvb21zICsgc3VyZmFjZV90b3RhbCArIHN1cmZhY2VfY292ZXJlZCArIHByb3BlcnR5X3R5cGUsIGRhdGEgPSBwcm9wZXJ0aWVzX3RyYWluKQp0aWR5X3Npbl9sMyA8LSB0aWR5KG1vZGVsb19zaW5fbDMsIGNvbmYuaW50ID0gVFJVRSkKdGlkeV9zaW5fbDMKYGBgClRvZG9zIGxvcyBwLXZhbG9yZXMgb2J0ZW5pZG9zIGRhbiBtdXkgcG9yIGRlYmFqbyBkZSAwLjA1LCB5IGxvcyBpbnRlcnZhbG9zIGRlIGNvbmZpYW56YSBlc3TDoW4gbXV5IGxlam9zIGRlbCAwLiBDb24gZXN0byBwb2RlbW9zIGNvbmNsdWlyIHF1ZSB0b2RhcyBsYXMgdmFyaWFibGVzIHNvbiBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhcy4gUG9kZW1vcyBvYnNlcnZhciBsYSB0YWJsYSBkZSBBbm92YSBwYXJhIGNvbmZpcm1hciBlc3RvIGNvbiBsYSB2YXJpYWJsZSBjYXRlZ8OzcmljYSBwcm9wZXJ0eV90eXBlLgoKYGBge3J9CnRpZHkoYW5vdmEobW9kZWxvX3Npbl9sMykpCmBgYAoKCkVmZWN0aXZhbWVudGUsIGVsIHRlc3QgRiByZWNoYXphIGxhIGhpcMOzdGVzaXMgZGUgcXVlIGxhIHBlbmRpZW50ZSBkZWwgcHJlY2lvIGRlIHVuYSBwcm9waWVkYWQgcmVzcGVjdG8gZGUgbGEgdmFyaWFibGUgcHJvcGVydHlfdHlwZSBlcyBpZ3VhbCBhIDAuIAoKUmVzcGVjdG8gYSBsb3MgdmFsb3JlcyBlc3RpbWFkb3MgZGUgbG9zIHBhcsOhbWV0cm9zIEJldGEsIG51ZXZhbWVudGUgZGVzdGFjYW1vcyBxdWUgZWwgcGFyw6FtZXRybyBhc29jaWFkbyBhIGxhIHZhcmlhYmxlIHJvb21zIGVzIG5lZ2F0aXZvOiBTaSB0ZW5lbW9zIGRvcyBwcm9waWVkYWRlcyBxdWUgZGlmaWVyZW4gw7puaWNhbWVudGUgZW4gcXVlIHVuYSB0aWVuZSB1bmEgaGFiaXRhY2nDs24gbcOhcyBxdWUgbGEgb3RyYSwgZW50b25jZXMgbGEgcHJvcGllZGFkIGNvbiB1bmEgaGFiaXRhY2nDs24gbcOhcyBlc3BlcmFtb3MgcXVlIHZhbGdhIDEzNjU3IGTDs2xhcmVzIHF1ZSBsYSBvdHJhIGhhYml0YWNpw7NuLiBFc3RlIHZhbG9yIGRhIGluY2x1c28gbWF5b3IgcXVlIGVsIGNvZWZpY2llbnRlIGRlIGxhIHZhcmlhYmxlIGRlIHJvb21zIHF1ZSBvYnNlcnZhbW9zIHByZXZpYW1lbnRlLgoKQ29tcGFyZW1vcyBsYSBlZmVjdGl2aWRhZCBkZSBhbWJvcyBtb2RlbG9zLiBDb21vIGVsIG1vZGVsbyBxdWUgdXNhIHRvZGFzIGxhcyB2YXJpYWJsZXMgdmEgYSBhanVzdGFyIG1lam9yIHBvciB0ZW5lciBtw6FzIG9wY2lvbmVzIHBhcmEgb3B0aW1pemFyLCB1dGlsaXphcmVtb3MgZWwgY29lZmljaWVudGUgZGUgUsKyIGFqdXN0YWRvIHkgbm8gZWwgUsKyIGNvbcO6biBzaW4gYWp1c3Rhci4gRGUgZXN0YSBmb3JtYSB2YW1vcyBhIHRlbmVyIHVuYSBmb3JtYSBtw6FzIGp1c3RhIGRlIG1lZGlyIHF1ZSBlZmVjdG8gdGllbmUgYWdyZWdhciBsYSB2YXJpYWJsZSBsMyBlbiBudWVzdHJvIG1vZGVsby4gCgpgYGB7cn0KbGlicmFyeShicm9vbSkKbW9kZWxzIDwtIGxpc3QobW9kZWxvX3RvdGFsID0gbW9kZWxvX3RvdGFsLG1vZGVsb19zaW5fbDMgPSBtb2RlbG9fc2luX2wzKQpkZl9ldmFsdWFjaW9uX3RyYWluID0gbWFwX2RmKG1vZGVscywgYnJvb206OmdsYW5jZSwgLmlkID0gIm1vZGVsIikgJT4lCiAgIyBvcmRlbmFtb3MgcG9yIFIyIGFqdXN0YWRvCiAgYXJyYW5nZShkZXNjKGFkai5yLnNxdWFyZWQpKQoKZGZfZXZhbHVhY2lvbl90cmFpbgoKYGBgClNlIHB1ZWRlIG9ic2VydmFyIHF1ZSBsb3MgdmFsb3JlcyBhanVzdGFkb3MgZGUgUsKyIG5vIGRpZmllcmVuIG11Y2hvIGRlIGxvcyB2YWxvcmVzIGRlIFLCsiBzaW4gYWp1c3Rhci4gQ29tbyBlbCB2YWxvciBhanVzdGFkbyBkZSBSwrIgZXMgbWVqb3IgZW4gZWwgbW9kZWxvIHRvdGFsLCBjb25jbHVpbW9zIHF1ZSBjb25zaWRlcmFyIGxhIGxvY2FsaWRhZCBkZSBsYSBwcm9waWVkYWQgdGllbmUgdW4gZWZlY3RvIHBvc2l0aXZvIGEgbGEgaG9yYSBhIGV4cGxpY2FyIGxhIHZhcmlhYmlsaWRhZCBkZWwgcHJlY2lvLiBFbiBvdHJhcyBwYWxhYnJhcywgZWwgbW9kZWxvIHF1ZSBjb25zaWRlcmEgYSB0b2RhcyBsYXMgdmFyaWFibGVzIGV4cGxpY2EgbWVqb3IgbGEgdmFyaWFiaWxpZGFkIGRlbCBwcmVjaW8gcXVlIGVsIG1vZGVsbyBxdWUgbm8gY29uc2lkZXJhIGxhIHZhcmlhYmxlIGwzLgoKI0NyZWFjacOzbiBkZSB2YXJpYWJsZXMKClBhcmEgY3JlYXIgdW4gbW9kZWxvIHN1cGVyaW9yIGEgbG9zIG1vZGVsb3MgcXVlIHZpbW9zIGFudGVzIHBvZGVtb3MgY3JlYXIgbnVldmFzIHZhcmlhYmxlcy4gVW5hIGZvcm1hIGRlIGhhY2VybG8gZXMgZXh0cmFlciBpbmZvcm1hY2nDs24gZGUgdW5hIGZ1ZW50ZSBleHRlcm5hLiBOb3MgdmFtb3MgYSBsaW1pdGFyIGEgY3JlYXIgdmFyaWFibGVzIHVzYW5kbyBpbmZvcm1hY2nDs24gZGUgbGFzIHZhcmlhYmxlcyBxdWUgeWEgdGVuZW1vcy4KCk5vcyBpbnRlcmVzYXLDrWEgYWdydXBhciBhIGxvcyBiYXJyaW9zIHBhcmEgcXVlIHNlYW4gbcOhcyBzaWduaWZpY2F0aXZvcy4gVmFtb3MgYSBhZ3J1cGFybG9zIHNlZ8O6biBlbCBwcmVjaW8gcG9yIG1ldHJvIGN1YWRyYWRvIHByb21lZGlvLiBQYXJhIGVzbyBwb2Ryw61hbW9zIHN1bWFyIHRvZG9zIGxvcyBwcmVjaW9zIGRlIGxhcyBwcm9waWVkYWRlcyBkZSBjYWRhIGJhcnJpbyB5IGRpdmlkaXJsb3MgcG9yIGVsIHRvdGFsIGRlIHN1cGVyZmljaWUgZW4gbWV0cm9zIGN1YWRyYWRvcyBkZSB0b2RhcyBlc2FzIHByb3BpZWRhZGVzLiBPdHJhIG9wY2nDs24gc2Vyw61hIGNhbGN1bGFyIGVsIHByZWNpbyBwb3IgbWV0cm8gY3VhZHJhZG8gZGUgY2FkYSBwcm9waWVkYWQgeSBsdWVnbyBhZ3J1cGFyIHBvciBsb2NhbGlkYWQgeSBwcm9tZWRpYXIuIFZhbW9zIGEgdXRpbGl6YXIgbGEgc2VndW5kYSBvcGNpw7NuIHF1ZSBhIG51ZXN0cm8gY3JpdGVyaW8gY2FsY3VsYSB1biBwcm9tZWRpbyBtw6FzIGhvbmVzdG8uCgpgYGB7cn0KCnByb3BlcnRpZXNfdHJhaW4gPC0gcHJvcGVydGllc190cmFpbiAlPiUKICBtdXRhdGUocHJlY2lvX3N1cGVyZmljaWUgPSBwcmljZS9zdXJmYWNlX3RvdGFsKQoKUHJvbWVkaW9fYmFycmlvcyA8LSB1bmlxdWUocHJvcGVydGllc190cmFpbiAlPiUKICBncm91cF9ieShsMykgJT4lCiAgbXV0YXRlKHByZWNpb19iYXJyaW8gPSBtZWFuKHByZWNpb19zdXBlcmZpY2llKSkgJT4lCiAgc2VsZWN0KGwzLHByZWNpb19iYXJyaW8pKSAKClByb21lZGlvX2JhcnJpb3MgIAoKYGBgClBhcmEgYWdydXBhciBhIGxvcyBiYXJyaW9zIHV0aWxpemFyZW1vcyBsb3MgY3VhcnRpbGVzIGRlIHByZWNpb19iYXJyaW8KYGBge3J9CnEgPC0gcXVhbnRpbGUoUHJvbWVkaW9fYmFycmlvcyRwcmVjaW9fYmFycmlvKQpxCmBgYAoKUHJpbWVybyBwcmVjaW9fYmFycmlvIGFsIGRhdGFzZXQgZGUgZW50cmVuYW1pZW50by4gVW5hIHZleiBoZWNobyBlc3RvIHBvZGVtb3MgcXVpdGFyIGxhIHZhcmlhYmxlIHByZWNpb19zdXBlcmZpY2llIHlhIHF1ZSBubyBsYSB2b2x2ZXJlbW9zIGEgdXRpbGl6YXIKCmBgYHtyfQpwcm9wZXJ0aWVzX3RyYWluIDwtcHJvcGVydGllc190cmFpbiAlPiUKICBncm91cF9ieShsMykgJT4lCiAgbXV0YXRlKHByZWNpb19iYXJyaW8gPSBtZWFuKHByZWNpb19zdXBlcmZpY2llKSkgJT4lCiAgc2VsZWN0KGlkLGwzLHJvb21zLGJhdGhyb29tcyxzdXJmYWNlX3RvdGFsLHN1cmZhY2VfY292ZXJlZCxwcmljZSxwcmVjaW9fYmFycmlvLHByb3BlcnR5X3R5cGUpCmNvbG5hbWVzKHByb3BlcnRpZXNfdHJhaW4pCmBgYAoKTG9zIGJhcnJpb3MgY3V5byBwcmVjaW8gcHJvbWVkaW8gcG9yIG1ldHJvIGN1YWRyYWRvIHNlcsOhbiBjbGFzaWZpY2Fkb3MgY29tbyBiYXJyaW9zIGRlIHByZWNpbyBiYWpvLCBsb3MgcXVlIHNlIGVuY3VlbnRyZW4gZWwgZW4gc2VndW5kbyB5IHRlcmNlciBjdWFydGlsIHNlcsOhbiBjbGFzaWZpY2Fkb3MgZGUgcHJlY2lvIG1lZGlvIHkgbG9zIHJlc3RhbnRlcyBzZXLDoW4gY2xhc2lmaWNhZG9zIGNvbW1vIGJhcnJpb3MgZGUgcHJlY2lvIGFsdG8uIERlIGVzdGEgZm9ybWEgbGEgbWl0YWQgZGUgbG9zIGJhcnJpb3Mgc2Vyw6FuIGNvbnNpZGVyYWRvcyBkZSBwcmVjaW8gbWVkaW8uCmBgYHtyfQpwcm9wZXJ0aWVzX3RyYWluIDwtIHByb3BlcnRpZXNfdHJhaW4gJT4lCiAgbXV0YXRlKGJhcnJpb3MgPSBjYXNlX3doZW4ocHJlY2lvX2JhcnJpbyA8PSBxWzJdIH4gJ3ByZWNpb19iYWpvJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVjaW9fYmFycmlvID4gcVs0XSB+ICdwcmVjaW9fYWx0bycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICdwcmVjaW9fbWVkaW8nKSkKCnByb3BlcnRpZXNfdHJhaW4KYGBgClJlYWxpY2Vtb3MgdW4gbW9kZWxvIGNvc2lkZXJhbmRvIGxhIHZhcmlhYmxlIGJhcnJpb3MgZW4gbHVnYXIgZGUgbDMKCmBgYHtyfQptb2RlbG9fYmFycmlvcyA8LSBsbShwcmljZSB+ICByb29tcyArIGJhdGhyb29tcyArIHN1cmZhY2VfdG90YWwgKyBzdXJmYWNlX2NvdmVyZWQgKyBwcm9wZXJ0eV90eXBlICsgYmFycmlvcywgZGF0YSA9IHByb3BlcnRpZXNfdHJhaW4pCnRpZHlfYmFycmlvcyA8LSB0aWR5KG1vZGVsb19iYXJyaW9zLCBjb25mLmludCA9IFRSVUUpCnRpZHlfYmFycmlvcwpgYGAKU2UgcHVlZGUgb2JzZXJ2YXIgcXVlIHRvZG9zIGxvcyBwLXZhbG9yZXMgY29ycmVzcG9uZGllbnRlcyBhICBjYWRhIHBhcsOhbWV0cm8gc29uIG11eSBwZXF1ZcOxb3MsIGluY2x1eWVuZG8gbGFzIGRvcyB2YXJpYWJsZXMgZHVtbXkgYWdyZWdhZGFzOiBiYXJyaW9zcHJlY2lvX2Jham8JIHkgYmFycmlvc3ByZWNpb19tZWRpby4gRXN0byBzaWduaWZpY2EgcXVlIGxvcyBlc3RpbWFkb3JlcyBkZSBjYWRhIGJldGEgc29uIHNpZ25pZmljYXRpdmFtZW50ZSBkaXN0aW50b3MgZGUgMC4gU2UgcHVlZGUgb2JzZXJ2YXIgcXVlIGVsIGVzdGltYWRvciBkZWwgQmV0YSBkZSBsYSB2YXJpYWJsZSByb29tcyBzaWd1ZSBzaWVuZG8gbmVnYXRpdm8sIHBvciBlbmRlIG51ZXN0cm9zIHByb2JsZW1hcyBkZSBjb2xpbmVhbGlkYWQgc2lndWVuIHBlcnNpc3RpZW5kby4KClJlc3BlY3RvIGRlIGxhcyBudWV2YXMgY2F0ZWdvcsOtYXMgYWdyZWdhZGFzLCBzZSBwdWVkZSBvYnNlcnZhciBxdWUgcHJlY2lvX2FsdG8gZXMgbGEgY2F0ZWdvcsOtYSBiYXNhbCB5YSBxdWUgbm8gaGF5IHVuYSBkdW1teSBiYXJyaW9zcHJlY2lvX2FsdG8uIEVzdG8gcXVpZXJlIGRlY2lyIHF1ZSwgZGUgdmFsZXIgMCBsYXMgdmFyaWFibGVzIGJhcnJpb3NwcmVjaW9fYmFqbyB5IGJhcnJpb3NwcmVjaW9fbWVkaW8sIGVudG9uY2VzIGxhIHByb3BpZWRhZCBhIGxhIHF1ZSBxdWVyZW1vcyBlc3RpbWFyIGVsIHByZWNpbyBlcyB1bmEgcHJvcGllZGFkIHViaWNhZGEgZW4gdW4gYmFycmlvIGNvbiBhbHRvIHByb21lZGlvIGRlIHByZWNpbyBwb3IgbWV0cm8gY3VhZHJhZG8uIExvcyBlc3RpbWFkb3JlcyBkZSBsb3MgYmV0YXMgY29ycmVzcG9uZGllbnRlcyBhIGNhZGEgdGlwbyBkZSBiYXJyaW8gbm9zIG11ZXN0cmFzIGN1YW50byB2YXLDrWEgZW4gcHJvbWVkaW8gZWwgcHJlY2lvIGRlIHVuYSBwcm9waWVkYWQgZGUgZXNlIHRpcG8gcmVzcGVjdG8gZGVsIHByZWNpbyBkZSB1bmEgcHJvcGllZGFkIHViaWNhZGEgZW4gdW4gYmFycmlvIGNvbiBhbHRvIHByZWNpbyBwb3IgbWV0cm8gY3VhZHJhZG8uIEFzw60sIHBvciBlamVtcGxvLCB1bmEgcHJvcGllZGFkIGRlIAlQYXJxdWUgQ2hhY2FidWNvLCBiYXJyaW8gZGUgcHJlY2lvcyBiYWpvcywgY29zdGFyw61hLCBlbiBwcm9tZWRpbywgODU3MDIgZG9sYXJlcyBtZW5vcyBxdWUgdW5hIHByb3BpZWRhZCBkZSBzaW1pbGFyZXMgY2FyYWN0ZXLDrXN0aWNhcyBwZXJvIHViaWNhZGEgZW4gUGFsZXJtbywgYmFycmlvIGRlIHByZWNpb3MgYWx0b3MuCgpBbmFsaWNlbW9zIGFob3JhIGVsIHRlc3QgRiBkZSBBbm92YQoKYGBge3J9CnRpZHkoYW5vdmEobW9kZWxvX2JhcnJpb3MpKQoKYGBgCkxvcyBwLXZhbG9yZXMgcXVlIGFycm9qYSBlbCB0ZXN0IHNvbiBwcsOhY3RpY2FtZW50ZSAwLCBwb3IgbG8gcXVlIGVsIHRlc3QgRiBub3MgZGljZSBxdWUgZWwgbW9kZWxvIGVzIHbDoWxpZG8uCgpWZWFtb3MgYWhvcmEgcXXDqSB0YW4gYnVlbm8gZXMgZXN0ZSBtb2RlbG8gZXhwbGljYW5kbyBsYSB2YXJpYWJpbGlkYWQgZGUgbG9zIGRhdG9zIGVuIGNvbXBhcmFjacOzbiBhIGxvcyBkb3MgbW9kZWxvcyBxdWUgaGFiw61hbW9zIGNyZWFkbyBwcmV2aWFtZW50ZS4KCmBgYHtyfQptb2RlbHMgPC0gbGlzdChtb2RlbG9fdG90YWwgPSBtb2RlbG9fdG90YWwsbW9kZWxvX3Npbl9sMyA9IG1vZGVsb19zaW5fbDMsIG1vZGVsb19iYXJyaW9zID0gbW9kZWxvX2JhcnJpb3MpCmRmX2V2YWx1YWNpb25fdHJhaW4gPSBtYXBfZGYobW9kZWxzLCBicm9vbTo6Z2xhbmNlLCAuaWQgPSAibW9kZWwiKSAlPiUKICAjIG9yZGVuYW1vcyBwb3IgUjIgYWp1c3RhZG8KICBhcnJhbmdlKGRlc2MoYWRqLnIuc3F1YXJlZCkpCgpkZl9ldmFsdWFjaW9uX3RyYWluCgpgYGAKRWwgbW9kZWxvIGNvbiB0b2RhcyBsYXMgdmFyaWFibGVzIG9yaWdpbmFsZXMgc2lndWUgZXhwbGljYW5kbyBtZWpvciBsYSB2YXJpYWJpbGlkYWQgZGUgbG9zIGRhdG9zIGRlIGFjdWVyZG8gYWwgY29lZmljaWVudGUgZGUgUsKyIGFqdXN0YWRvLiBTZSBwdWVkZSBvYnNlcnZhciBkZSB0b2RvcyBtb2RvcyBxdWUgbGEgZGlmZXJlbmNpYSBlbnRyZSBlbCBSwrIgeSBlbCBSwrIgYWp1c3RhZG8gbm8gZXMgbXV5IGFsdGEgZGViaWRvIGFsIGdyYW4gbsO6bWVybyBkZSBvYnNlcnZhY2lvbmVzIHF1ZSB0ZW5lbW9zIGRpc3BvbmlibGVzLCBsbyBxdWUgaGFjZSBxdWUgZWwgZWZlY3RvIG5vY2l2byBkZSBhZ3JlZ2FyIHZhcmlhYmxlcyBpcnJlbGV2YW50ZXMgYSBsb3MgZGF0b3Mgbm8gc2UgdmVhIHRhbiByZWZsZWphZG8uIAoKRWwgbW9kZWxvIG51ZXZvIHByZXNlbnRhIHVuYSBtZWpvcmEgY29uc2lkZXJhYmxlIHJlc3BlY3RvIGFsIG1vZGVsbyBzaW4gbGEgdmFyaWFibGUgbDMuIEFkZW3DoXMsIGEgZGlmZXJlbmNpYSBkZWwgbW9kZWxvIGNvbiB0b2RhcyBsYXMgdmFyaWFibGVzLCBsYSBpbmNsdXNpw7NuIGRlIHRvZGFzIGxhcyB2YXJpYWJsZXMgZHVtbXkgZXMgc2lnbmlmaWNhdGl2YSwgbG8gcXVlIGxlIGRhIGFsIG1vZGVsbyBjaWVydGEgcm9idXN0ZXouIE90cmEgdmVudGFqYSBkZWwgbW9kZWxvIG51ZXZvIGVzIHF1ZSBlcyBtdWNobyBtw6FzIHNlbmNpbGxvIHF1ZSBlbCBtb2RlbG8gZG9uZGUgdXNhYmFtb3MgdG9kYXMgbGFzIGR1bW1pZXMgZGUgbG9zIGJhcnJpb3MuCgpMYSDDum5pY2EgZGVzdmVudGFqYSBkZWwgbW9kZWxvIG51ZXZvLCBhZGVtw6FzIGRlIHN1IG3DoXMgYmFqbyBSwrIsIGVzIHF1ZSBsYSB2YXJpYWJsZSBjYXRlZ8OzcmljYSBiYXJyaW9zIGRlcGVuZGUgZGVsIHByZWNpbyBkZSBsYXMgcHJvcGllZGFkZXMgZGVsIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG8sIGxvIHF1ZSBwb2Ryw61hIGhhY2VyIHF1ZSB1biBiYXJyaW8gY29uIG5vIGRlbWFzaWFkYXMgb2JzZXJ2YWNpb25lcyB0ZXJtaW5lIGVuIHVuYSBjYXRlZ29yw61hIGluY29ycmVjdGEgcG9yIGxvcyBkYXRvcyBkZSBlbnRyZW5hbWllbnRvIHF1ZSBoYXkgZGlzcG9uaWJsZXMuIERlIHRvZG9zIG1vZG9zLCB1biBiYXJyaW8gY29uIHBvY2FzIG9ic2VydmFjaW9uZXMgcG9kcsOtYSByZXBlcmN1dGlyIGHDum4gbXVjaG8gcGVvciBlbiBlbCBtb2RlbG8gY29uIGxhcyBkdW1taWVzIHBvciBjYWRhIGJhcnJpbywgeWEgcXVlIGVsIGVzdGltYWRvciBwYXJhIGVzYSBkdW1teSBkYXLDrWEgbXV5IGluZXhhY3RvLgoKTGEgdmFyaWFibGVzIGwzIHkgYmFycmlvcyBubyBzZSBwb2Ryw61hbiBnZW5lcmFsaXphciBwYXJhIGJhcnJpb3MgbnVldm9zLCBwZXJvIHNlcsOtYSBtw6FzIGbDoWNpbCBhcnJlZ2xhciBlc3RlIHByb2JsZW1hIGVuIGxhIHZhcmlhYmxlIGJhcnJpb3MgeWEgcXVlIHNvbG8gdGVuZHLDrWFtb3MgcXVlIGlkZW50aWZpY2FyIHNpIGVsIGJhcnJpbyBudWV2byB0aWVuZSBwcmVjaW9zIGFsdG9zLCBiYWpvcyBvIG1lZGlhbm9zIG1lZGlhbnRlIGFsZ3VuYSBtdWVzdHJhIGRhZGEgc2luIG5lY2VzaWRhZCBkZSBhcm1hciB1biBtb2RlbG8gbnVldm8gcGFyYSBlc2UgYmFycmlvLgoKRW4gY29uY2x1c2nDs24sIGNvbnNpZGVybyBxdWUgZWwgbW9kZWxvIHF1ZSB1dGlsaXphIGxhIHZhcmlhYmxlIGJhcnJpbyBwdWVkZSBzZXIgbcOhcyDDunRpbCBxdWUgZWwgcXVlIGNvbnNpZGVyYSBsYSB2YXJpYWxibGUgbDMgcG9yIHN1IHNpbXBsZXphLCBhIHBlc2FyIGRlIHF1ZSBlbCBjb2VmaWNpZW50ZSBkZSBSwrIgYWp1c3RhZG8gc2VhIG1lbm9yLgoKUGFyYSBzb2x1Y2lvbmFyIG51ZXN0cm9zIHByb2JsZW1hcyBkZSBjb2xpbmVhbGlkYWQsIHZhbW9zIGEgYXJtYXIgdW4gbnVldm8gbW9kZWxvIHV0aWxpemFuZG8sIGVuIHZleiBkZSBzdXJmYWNlX3RvdGFsLCB1bmEgdmFyaWFibGUgcXVlIG1pZGEgbGEgc3VwZXJmaWNpZSBubyBjdWJpZXJ0YS4gRXN0byBsbyBoYWNlbW9zIHBvcnF1ZSBzdXJmYWNlX3RvdGFsIGVzdMOhIG11eSBjb3JyZWxhY2lvbmFkYSBjb24gc3VyZmFjZV9jb3ZlcmVkCgpgYGB7cn0KcHJvcGVydGllc190cmFpbiA8LQogIHByb3BlcnRpZXNfdHJhaW4gJT4lCiAgbXV0YXRlKHN1cGVyZmljaWVfZGVzY3ViaWVydGEgPSAoc3VyZmFjZV90b3RhbC1zdXJmYWNlX2NvdmVyZWQpKQoKCmBgYAoKVmVhbW9zIGxvcyByZXN1bHRhZG9zIGRlIGluY2x1aXIgbGEgdmFyaWFibGUgc3VwZXJmaWNpZV9kZXNjdWJpZXJ0YSBhbCBtb2RlbG8gZW4gbHVnYXIgZGUgc3VyZmFjZV90b3RhbAoKYGBge3J9Cm1vZGVsb19kZXNjdWJpZXJ0YSA8LSBsbShwcmljZSB+ICByb29tcyArIGJhdGhyb29tcyArIHN1cGVyZmljaWVfZGVzY3ViaWVydGEgKyBzdXJmYWNlX2NvdmVyZWQgKyBwcm9wZXJ0eV90eXBlICsgYmFycmlvcywgZGF0YSA9IHByb3BlcnRpZXNfdHJhaW4pCnRpZHlfZGVzY3ViaWVydGEgPC0gdGlkeShtb2RlbG9fZGVzY3ViaWVydGEsIGNvbmYuaW50ID0gVFJVRSkKdGlkeV9kZXNjdWJpZXJ0YQpgYGAKQW5hbGl6YW5kbyBsb3MgcC12YWxvcmVzIG9idGVuaWRvcywgbm90YW1vcyBxdWUgdG9kYXMgbGFzIHZhcmlhYmxlcyBjb25zaWRlcmFkYXMgc29uIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdmFzLiBOdWV2YW1lbnRlIGVsIGVzdGltYWRvciBkZSBsYSBwZW5kaWVudGUgZGVsIHByZWNpbyBlbiBmdW5jacOzbiBkZSBsYSB2YXJpYWJsZSByb29tcyBlcyBuZWdhdGl2by4gTG9zIGVzdGltYWRvcmVzIGRlIGxvcyBiZXRhcyBkZSBzdXJmYWNlX2NvdmVyZWQgeSBzdXBlcmZpY2llX2Rlc2N1YmllcnRhIHNvbiAyNTI5IHkgODUxIHJlc3BlY3RpdmFtZW50ZS4gRXN0byBzaWduaWZpY2EgcXVlLCBwb3IgY2FkYSBtZXRybyBjdWFkcmFkbyBkZSBzdXBlcmZpY2llIGN1YmllcnRhIGFncmVnYWRvLCBlbCBwcmVjaW8gZGUgdW5hIHByb3BpZWRhZCBhdW1lbnRhLCBlbiBwcm9tZWRpbywgMjUyOSBkw7NsYXJlczsgbWllbnRyYXMgcXVlIHNpIGFncmVnYW1vcyB1biBtZXRybyBjdWFkcmFkbyBkZSBzdXBlcmZpY2llIGRlc2N1YmllcnRhIGVsIHByZWNpbyBhdW1lbnRhLCBlbiBwcm9tZWRpbywgODUxIGTDs2xhcmVzLCBxdWUgZXMgYXByb3hpbWFkYW1lbnRlIGxhIHRlcmNlcmEgcGFydGUuIFNlIHB1ZWRlIHZlciwgZGUgaGVjaG8sIHF1ZSBsb3MgZXN0aW1hZG9yZXMgZGFuIGlndWFsIGVuIGxvcyBkb3MgbW9kZWxvcyBwYXJhIHRvZGFzIGxhcyB2YXJpYWJsZXMgZXhjZXB0byBzdXJmYWNlX2NvdmVyZWQsIHkgZWwgZXN0aW1hZG9yIGRlIGxhIHBlbmRpZW50ZSBkZWwgcHJlY2lvIHJlc3BlY3RvIGRlIHN1cmZhY2VfdG90YWwgeSBzdXBlcmZpY2llX2Rlc2N1YmllcnRhIGNvaW5jaWRlbiwgeS4gQWRlbcOhcywgbGEgZGlmZXJlbmNpYSBlbnRyZSBsb3MgZXN0aW1hZG9yZXMgZGVsIGJldGEgZGUgc3VyZmFjZV9jb3ZlcmVkIGVuIGFtYm9zIG1vZGVsb3MgZXMgZWwgZXN0aW1hZG9yIGRlbCBiZXRhIGRlIHN1cGVyZmljaWVfZGVzY3ViaWVydGEuIERpY2hhIGRpZmVyZW5jaWEgc2UgZGViZSBhIHF1ZSBlbiBlbCBtb2RlbG8gZG9uZGUgY29uc2lkZXJhbW9zIHN1cmZhY2VfdG90YWwgZXN0YW1vcyBjb250YW5kbyBsYSBzdXBlcmZpY2llIGRlc2N1YmllcnRhIDIgdmVjZXMgYWwgY29uc2lkZXJhciB0YW50byBzdXJmYWNlX2NvdmVyZWQgY29tbyBzdXJmYWNlX3RvdGFsLiBBc8OtIHF1ZSBlc2NlbmNpYWxtZW50ZSBhbWJvcyBtb2RlbG9zIHNvbiBpZ3VhbGVzLiAKClZlYW1vcyByw6FwaWRhbWVudGUgZWwgdGVzdCBGIHBhcmEgY29tcHJvYmFyIGxhIHZhbGlkZXogZGVsIG1vZGVsby4KCmBgYHtyfQp0aWR5KGFub3ZhKG1vZGVsb19kZXNjdWJpZXJ0YSkpCgpgYGAKTG9zIHAtdmFsb3JlcyBzb24gdG9kb3MgcHLDoWN0aWNhbWVudGUgMC4gRWwgbW9kZWxvIHNlZ8O6biBlbCB0ZXN0IEYgZXMgdsOhbGlkby4KCkFob3JhIGNvbXBhcmVtb3MgbG9zIGNvZWZpY2llbnRlcyBkZSBSwrIgYWp1c3RhZG8gZGUgY2FkYSBtb2RlbG8gCgoKYGBge3J9Cm1vZGVscyA8LSBsaXN0KG1vZGVsb190b3RhbCA9IG1vZGVsb190b3RhbCxtb2RlbG9fc2luX2wzID0gbW9kZWxvX3Npbl9sMywgbW9kZWxvX2JhcnJpb3MgPSBtb2RlbG9fYmFycmlvcywgbW9kZWxvX2Rlc2N1YmllcnRhID0gbW9kZWxvX2Rlc2N1YmllcnRhKQpkZl9ldmFsdWFjaW9uX3RyYWluID0gbWFwX2RmKG1vZGVscywgYnJvb206OmdsYW5jZSwgLmlkID0gIm1vZGVsIikgJT4lCiAgIyBvcmRlbmFtb3MgcG9yIFIyIGFqdXN0YWRvCiAgYXJyYW5nZShkZXNjKGFkai5yLnNxdWFyZWQpKQoKZGZfZXZhbHVhY2lvbl90cmFpbgoKYGBgCgpMb3MgY29lZmljaWVudGVzIGRlIFLCsiBhanVzdGFkbyBkZSBhbWJvcyBtb2RlbG9zIGNvaW5jaWRlbi4gRXN0byBzZSBkZWJlIGEgcXVlIGFtYm9zIG1vZGVsb3MgZXNjZW5jaWFsbWVudGUgc29uIGxvIG1pc21vLiBEZSB0b2RvcyBtb2RvcywgZWwgbW9kZWxvIGNvbiBzdXBlcmZpY2llIGRlc2N1YmllcnRhIGVzIG3DoXMgZsOhY2lsIGRlIGludGVycHJldGFyIHlhIHF1ZSBubyBwcmVzZW50YSBzb2xhcGFtaWVudG9zIGVudHJlIGxhcyB2YXJpYWJsZXMgZGUgc3VwZXJmaWNpZS4KCiNEaWFnbsOzc3RpY28gZGVsIG1vZGVsbyBkZSBzdXBlcmZpY2llIGRlc2N1YmllcnRhCgpFbiBlc3RhIHNlY2Npw7NuIG9ic2VydmFyZW1vcyBsb3MgcmVzaWR1b3MgZGVsIG1vZGVsbyBwYXJhIGNvcnJvYm9yYXIgcXVlIHNlIHZlcmlmaWNhbiBsb3Mgc3VwdWVzdG9zIGRlbCBtaXNtby4gT2JzZXJ2ZW1vcyBlbCBkaWFnbsOzc3RpY28gZGVsIG1vZGVsbyBkb25kZSBjb25zaWRlcmFtb3MgbGEgdmFyaWFibGUgYmFycmlvcyBlbiBsdWdhciBkZSBsMyB5IGxhIHZhcmlhYmxlIHN1cGVyZmljaWVfZGVzY3ViaWVydGEgZW4gbHVnYXIgZGUgc3VyZmFjZV90b3RhbC4KCmBgYHtyfQpwbG90KG1vZGVsb19kZXNjdWJpZXJ0YSkKYGBgClJlc2lkb3VzIHZzIHZhbG9yZXMgcHJlZGljaG9zOiBQYXJhIHF1ZSBsb3Mgc3VwdWVzdG9zIHNlIGN1bXBsYW4sIGxvcyByZXNpZHVvcyBubyBkZWJlcsOtYW4gdGVuZXIgZXN0cnVjdHVyYS4gRWwgZ3LDoWZpY28gZGUgcmVzaWR1b3MgdnMgdmFsb3JlcyBwcmVkaWNob3MgZGViZXLDrWEgZm9ybWFyIHVuYSBudWJlIHVuaWZvcm1lIGRlIHB1bnRvcy4gU2UgcHVlZGUgdmVyLCBzaW4gZW1iYXJnbywgcXVlIGxvcyByZXNpZHVvcyB2YXLDrWFuIG3DoXMgcGFyYSB2YWxvcmVzIHByZWRpY2hvcyBtZWRpYW5hbWVudGUgZ3JhbmRlcyAoYWwgcmVkZWRvciBkZSA0MDAwMDApLgoKUVEgcGxvdDogTG9zIGV4dHJlbW9zIGluZmVyaW9yIGl6cXVpZXJkbyB5IHN1cGVyaW9yIGRlcmVjaG8gbm8gc2UgYWp1c3RhbiBhIGxhIGRpc3RyaWJ1Y2nDs24gdGXDs3JpY2EuIAoKUmVzaWR1b3MgZXN0YW5kYXJpemFkb3MgdnMgdmFsb3JlcyBwcmVkaWNob3M6IExhIHBlbmRpZW50ZSBkZWwgZ3LDoWZpY28gZXMgcG9zaXRpdmEsIGxvIHF1ZSBtdWVzdHJhIHF1ZSBsb3MgcmVzaWR1b3MgZXN0YW5kYXJpemFkb3MgdGllbmVuIHVuYSB0ZW5kZW5jaWEgYSBhdW1lbnRhciBhIG1lZGlkYSBxdWUgYXVtZW50YSBlbCB2YWxvciBwcmVkaWNoby4KClJlc2lkdW9zIHZzIExldmVyYWdlOiBIYXkgdW5hIG9ic2VydmFjacOzbiBxdWUgdGllbmUgdW4gbGV2ZXJhZ2UgbXV5IGFsdG8uIEVsIHJlc2lkdW8gYXNvY2lhZG8gYSBlc2UgdmFsb3IgZXMgYmFzdGFudGUgYWx0byB0YW1iacOpbiwgbG8gcXVlIGluZGljYSBxdWUgZXNlIHZhbG9yIHBvZHLDrWEgcmVwZXJjdXRpciBtdWNobyBlbiBsYSBjYWxpZGFkIGRlbCBtb2RlbG8uCgpCdXNxdWVtb3MgbGEgb2JzZXJ2YWNpw7NuIGNvbiBhbHRvIGxldmVyYWdlCgpgYGB7cn0KYXVfbW9kZWxvcyA9IHB1cnJyOjptYXBfZGYobW9kZWxzLCBicm9vbTo6YXVnbWVudCwgLmlkID0gIm1vZGVsIikKCmF1X21vZGVsb3MgJT4lIGZpbHRlcihtb2RlbCA9PSAibW9kZWxvX2Rlc2N1YmllcnRhIikgJT4lCiAgZmlsdGVyKC5oYXQgPT0gbWF4KC5oYXQpKQpgYGAKUGFyZWNpZXJhIHNlciB1bmEgb2JzZXJ2YWNpw7NuIGNvbiB2YWxvcmVzIGZhbHRhbnRlcyBlbiBsMyB5IGVuIHN1cmZhY2VfdG90YWwgKHkgcG9yIGVuZGUsIGVuIHN1cGVyZmljaWVfZGVzY3ViaWVydGEpLgoKCkRlYmlkbyBhIGxhIHByZXNlbmNpYSBkZSBvYnNlcnZhY2lvbmVzIGNvbiBhbHRvIGxldmVyYWdlLCBsYSBmYWx0YSBkZSBub3JtYWxpZGFkIHkgIGxhIGZhbHRhIGRlIGhvbW9jZWRhc3RpY2lkYWQgZGV0ZWN0YWRhLCBjb25jbHVpbW9zIGNvbW8gZGlhZ27Ds3N0aWNvIHF1ZSBlbCBtb2RlbG8gZGUgc3VwZXJmaWNpZSBkZXNjdWJpZXJ0YSBubyBzYXRpc2ZhY2UgbG9zIHN1cHVlc3RvcyBkZWwgbW9kZWxvIGxpbmVhbC4KCiNNb2RlbG8gY29uIExvZ2FyaXRtbwoKRW4gZXN0YSBzZWNjacOzbiB2YW1vcyBhIGNyZWFyIHVuIG1vZGVsbyBsaW5lYWwgdXNhbmRvIHZhcmlhYmxlcyBlc2NhbGFkYXMgbG9nYXLDrXRtaWNhbWVudGUuIEVsIG9iamV0aXZvIGRlIGVzdGUgYW7DoWxpc2lzIGVzIHJlbW92ZXIgbGFzIGxpbWl0YWNpb25lcyBkZWwgbW9kZWxvIGxpbmVhbCBwYXJhIGV4cGxpY2FyIGxhIHZhcmlhY2nDs24gZGVsIHByZWNpbyBjdWFuZG8gZGljaGFzIHZhcmlhY2lvbmVzLCBlbiBmdW5jacOzbiBkZSBsYXMgdmFyaWFibGVzIHF1ZSB0ZW5lbW9zIGEgbnVlc3RyYSBkaXNwb3NpY2nDs24sIHNvbiBleHBvbmVuY2lhbGVzLiBMYXMgdmFyaWFibGVzIHF1ZSB2YW1vcyBhIGNvbXBvbmVyIGNvbiBsb2dhcml0bW8gc29uIHByaWNlLCByb29tcywgYmF0aHJvb21zIHkgc3VyZmFjZV9jb3ZlcmVkLiBUYW1iacOpbiB1c2FyZW1vcyBsYXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhcyBwcm9wZXJ0eV90eXBlIHkgYmFycmlvcyB5IGxhIHZhcmlhYmxlIG51bcOpcmljYSBzdXBlcmZpY2llX2Rlc2N1YmllcnRhLCBsYSBjdWFsIG5vIGxlIHRvbWFtb3MgbG9nYXJpdG1vIHBvcnF1ZSBwb2Ryw61hbiBoYWJlciBwcm9ibGVtYXMgZGUgZG9taW5pbyBzaSBlc3RhIHRvbWEgZWwgdmFsb3IgMC4KCkNvbWVuY2Vtb3MgY3JlYW5kbyBsYXMgdmFyaWFibGVzIGxvZ2FydGltby4KCmBgYHtyfQpwcm9wZXJ0aWVzX3RyYWluIDwtIHByb3BlcnRpZXNfdHJhaW4gJT4lCiAgbXV0YXRlKGxvZ19wcmljZSA9IGxvZyhwcmljZSksIGxvZ19yb29tcyA9IGxvZyhyb29tcyksIGxvZ19iYXRocm9vbXMgPSBsb2coYmF0aHJvb21zKSwgbG9nX3N1cmZhY2VfY292ZXJlZCA9IGxvZyhzdXJmYWNlX2NvdmVyZWQpKQoKaGVhZChwcm9wZXJ0aWVzX3RyYWluKQpgYGAKClByb2NlZGFtb3MgZW50b25jZXMgYSBnZW5lcmFyIGVsIG1vZGVsbyB5IGVzdHVkaWFyIGxvcyByZXN1bHRhZG9zIGRlbCBtaXNtbwoKYGBge3J9Cm1vZGVsb19sb2c8LSBsbShsb2dfcHJpY2UgfiAgbG9nX3Jvb21zICsgbG9nX2JhdGhyb29tcyArIHN1cGVyZmljaWVfZGVzY3ViaWVydGEgKyBsb2dfc3VyZmFjZV9jb3ZlcmVkICsgcHJvcGVydHlfdHlwZSArIGJhcnJpb3MsIGRhdGEgPSBwcm9wZXJ0aWVzX3RyYWluKQp0aWR5X2xvZyA8LSB0aWR5KG1vZGVsb19sb2csIGNvbmYuaW50ID0gVFJVRSkKdGlkeV9sb2cKYGBgCgpTZSBwdWVkZSBvYnNlcnZhciBxdWUgbG9zIHAtdmFsb3JlcyBkZSBjYWRhIHTDqXJtaW5vIHNvbiBtdXkgcGVxdWXDsW9zIHkgbG9zIGludGVydmFsb3MgZGUgY29uZmlhbnphIGVzdMOhbiBtdXkgYWxlamFkb3MgZGVsIDAuIE9ic2VydmVtb3MgdGFtYmnDqW4gZWwgdGVzdCBGIHBhcmEgYXNlZ3VyYXJub3MgcXVlIGVsIG1vZGVsbyBjb24gbG9nYXJpdG1vIHRpZW5lIHZhbGlkZXoKCsK0CmBgYHtyfQp0aWR5KGFub3ZhKG1vZGVsb19sb2cpKQoKYGBgCgpMYSB0YWJsYSBkZSBhbm92YSBtdWVzdHJhIHF1ZSBsb3MgcC12YWxvcmVzIHNvbiBwcsOhY3RpY2FtZW50ZSB0b2RvcyAwLCBsbyBxdWUgdmFsaWRhIGxvcyByZXN1bHRhZG9zIHF1ZSBwb2RlbW9zIGludGVycHJldGFyIGRlbCBtb2RlbG8uIAoKQ29tbyBlc3RhbW9zIHRvbWFuZG8gbG9nYXJpdG1vIHNvYnJlIGVsIHByZWNpbywgZXMgZGUgZXNwZXJhciBxdWUgbGFzIGVzdGltYWNpb25lcyBkZSBsYSBwZW5kaWVudGUgZGVsIGxvZ2FyaXRtbyBkZWwgcHJlY2lvIHJlc3BlY3RvIGRlIGNhZGEgdmFyaWFibGUgZGVsIG1vZGVsbyBkaXNtaW51eWFuIGNvbnNpZGVyYWJsZW1lbnRlLiBBc8OtLCBwb3IgZWplbXBsbywgZWwgZXN0aW1hZG9yIGRlbCBiZXRhIGRlIGxvZ19yb29tcyBzaWd1ZSBzaWVuZG8gbmVnYXRpdm8gcGVybyBlc3RhIHZleiBlcyBkZSB0YW4gc29sbyAtMC4wNS4gUGFyYSBxdWUgZWwgbG9nYXJpdG1vIGRlIHJvb21zIHN1YmEgMSBuZWNlc2l0YXLDrWFtb3MgcXVlIGVsIG7Dum1lcm8gZGUgaGFiaXRhY2lvbmVzIHNlYSBtdWx0aXBsaWNhZG8gcG9yIGVsIG7Dum1lcm8gZSAoZXMgZGVjaXIsIGFwcm94aW1hZGFtZW50ZSBwb3IgMi43MTgpLiBEYWRvIHF1ZSBsYSBjYW50aWRhZCBkZSBoYWJpdGFjaW9uZXMgZXMgdW4gbsO6bWVybyBlbnRlcm8sIGVzIHJhcm8gcXVlIGVzdG8gc3VjZWRhLCBwZXJvIHBvZGVtb3MgZGFyIHVuIGVqZW1wbG8gYXByb3hpbWFkby4gCgpTdXBvbmdhbW9zIHF1ZSB0ZW5lbW9zIHVuYSBwcm9waWVkYWQgY29uIDQgaGFiaXRhY2lvbmVzLCB5IG90cmEgY29uIDExIGhhYml0YWNpb25lcy4gMTEgZXMgNCAqIDIuNzUsIGVzIGRlY2lyLCA0KmUgZXMgYXByb3hpbWFkYW1lbnRlIDExLiBEZSB0ZW5lciBhbWJhcyBwcm9waWVkYWRlcyBzaW1pbGFyZXMgdmFsb3JlcyBlbiBlbCByZXN0byBkZSBsYXMgdmFyaWFibGVzLCB0ZW5kcsOtYW1vcywgYSBlZmVjdG9zIGRlbCBtb2RlbG8sIDIgb2JzZXJ2YWNpb25lcyBzaW1pbGFyZXMgZXhjZXB0byBxdWUgZW4gbG9nX3Jvb21zIHVuYSB2YWxlIGxvZyg0KSA9IDEuMzgsIHkgbGEgb3RyYSB2YWxlIGxvZygxMSkgPSAyLjM5LiAKCkNvbW8gZWwgdmFsb3IgZGUgbG9nX3Jvb21zIGF1bWVudGEgKGFwcm94aW1hZGFtZW50ZSkgZW4gMSwgZW50b25jZXMgbGEgZXN0aW1hY2nDs24gZGVsIGxvZ2FyaXRtbyBkZWwgcHJlY2lvIGVuIGxhIHByaXBpZWRhZCBjb24gMTEgaGFiaXRhY2lvbmVzIGRpc21pbnVpcsOtYSByZXNwZWN0byBkZSBsYSBkZSA0IGhhYml0YWNpb25lcyBlbiAwLjA0OC4gU2kgbGxhbWFtb3MgQSBhIGxhIGVzdGltYWNpw7NuIGRlbCBwcmVjaW8gZGUgbGEgcHJvcGllZGFkIGNvbiA0IGhhYml0YWNpb25lcyB5IEIgYSBsYSBlc3RpbWFjacOzbiBkZWwgcHJlY2lvIGRlIGxhIHByb3BpZWRhZCBjb24gMTEgaGFiaXRhY2lvbmVzIChjb24gZXN0aW1hY2nDs24gZGVsIHByZWNpbyBub3MgcmVmZXJpbW9zIGEgbGEgZXhwb25lbmNpYWwgZGUgbGEgZXN0aW1hY2nDs24gZGVsIGxvZ2FyaXRtbyBkZWwgcHJlY2lvKSwgZW50b25jZXMgdGVuZW1vcyBxdWUKCkxvZyhBKSAtIExvZyhCKSA9IDAuMDQ4IApMb2coQS9CKSA9IDAuMDQ4CgpMdWVnbyBBL0IgPSBlXigwLjA0OCkgPT4gQiA9IEEgKiBlXigtMC4wNDgpLCBxdWUgZXMgYXByb3hpbWFkYW1lbnRlIEEgKiAwLjk1CgpEZSBlc3RvIGZpbmFsbWVudGUgcG9kZW1vcyBjb25jbHVpciBxdWUgc2kgbXVsdGlwbGljYW1vcyBwb3IgZSBlbCBuw7ptZXJvIGRlIGhhYml0YWNpb25lcyBwcmVzZXJ2YW5kbyBlbCB2YWxvciBkZWwgcmVzdG8gZGUgbGFzIHZhcmlhYmxlcywgc2UgZXNwZXJhIGVudG9uY2VzIHF1ZSBlbCB2YWxvciBkZSBsYSBwcm9waWVkYWQgZGlzbWludXlhIHVuIDUlLiBDb21vIHlhIGNvbWVudGFtb3MgYW50ZXJpb3JtZW50ZSwgZXN0ZSBhbsOhbGlzaXMgZXMgcG9jbyDDunRpbCBlbiBsYSBwcsOhY3RpY2EgeWEgcXVlIGVzIHBvY28gcmVhbGlzdGEgcXVlIHBvZGFtb3MgYXVtZW50YXIgdGFudG8gZWwgbsO6bWVybyBkZSBsYXMgaGFiaXRhY2lvbmVzIHByZXNlcnZhbmRvIGVsIHJlc3RvIGRlIGxhcyB2YXJpYWJsZXMgZmlqYXMuIFVuIGFuw6FsaXNpcyBzaW1pbGFyIHBvZGVtb3MgaGFjZXIgY29uIGVsIHJlc3RvIGRlIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcyBhIGxhcyBxdWUgbGVzIGFwbGljYW1vcyBsb2dhcml0bW8sIGF1bnF1ZSBlbiBlc29zIGNhc29zLCBtdWx0aXBsaWNhciBlbCB2YWxvciBkZSBjYWRhIHZhcmlhYmxlIHBvciBlIHRlbmRyw6EgdW4gZWZlY3RvIG11bHRpcGxpY2F0aXZvIHBvc2l0aXZvIHNvYnJlIGVsIHByZWNpby4KClJlc3BlY3RvIGRlIGxhIHZhcmlhYmxlIHN1cGVyZmljaWVfZGVzY3ViaWVydGEsIGVsIGludGVyY2VwdCBkZSAwLjAwMzggbm9zIGRpY2UgcXVlLCBkZSAnYWdyZWdhcicgdW4gbWV0cm8gY3VhZHJhZG8gZGUgc3VwZXJmaWNpZSBzaW4gY3VicmlyLCBlbCBsb2dhcml0bW8gZGVsIHByZWNpbyBhdW1lbnRhcsOhLCBlbiBwcm9tZWRpbywgMC4wMDM4LiBFc3RvIHNlIHRyYWR1Y2UgYSBxdWUgZWwgcHJlY2lvIHNlIGVzcGVyYSBxdWUgc2UgbXVsdGlwbGlxdWUgcG9yIGVeKDAuMDAzOCkgPSAxLjAwMy4KClJlc3BlY3RvIGEgbGFzIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMsIHRvbWVtb3MgcG9yIGVqZW1wbG8gYmFycmlvcy4gTGEgY2F0ZWdvcsOtYSBiYXNhbCBlbiBlc2UgY2FzbyBlcyBwcmVjaW9fYWx0by4gRWwgZXN0aW1hZG9yIGRlbCBiZXRhIGRlIGxhIHZhcmlhYmxlIGR1bW15IGRlIHByZWNpb19iYWpvIGVzIC0wLjQzNzU4Nzg2My4gRXN0byBzaWduaWZpY2EgcXVlLCBkZSB0ZW5lciBkb3MgcHJvcGllZGFkZXMgY29uIHNpbWlsYXJlcyBjYXJhY3RlcsOtc3RpY2FzLCBzb2xvIHF1ZSB1bmEgZXMgZGUgdW4gYmFycmlvIGRlIHByZWNpb3MgYWx0b3MgeSBsYSBvdHJhIGVzIGRlIHVuIGJhcnJpbyBkZSBwcmVjaW9zIGJham9zLCBlbnRvbmNlcyBsYSBkaWZlcmVuY2lhIGVuIHTDqXJtaW5vcyBkZWwgbG9nYXJpdG1vIGRlbCBwcmVjaW8gZGUgbGFzIGRvcyBwcm9waWVkYWRlcyBzZXLDoSwgZW4gcHJvbWVkaW8gMC40Mzc2LCBzaWVuZG8gbGEgcHJvcGllZGFkIGRlbCBiYXJyaW8gbcOhcyBjYXJvIGxhIHF1ZSB0aWVuZSBlbCBwcmVjaW8gbcOhcyBlbGV2YWRvLiBEZSBlc3RvIGNvbmx1w61tb3MgbGEgcHJvcG9yY2nDs24gZGVsIHByZWNpbyBkZSB1bmEgcHJvcGllZGFkIGRlIHVuIGJhcnJpbyBodW1pbGRlIHJlc3BlY3RvIGRlbCBwcmVjaW8gZGUgdW5hIHByb3BpZWRhZCBjb24gc2ltaWxhcmVzIGNhcmFjdGVyw61zdGljYXMgcHJvdmVuaWVudGUgZGUgdW4gYmFycmlvIGx1am9zbyBlcywgZW4gcHJvbWVkaW8sIGVeKC0wLjQzNzYpID0gMC42NDU1LiBFbiBvdHJhcyBwYWxhYnJhcywgdW5hIHByb3BpZWRhZCBkZSB1biBiYXJyaW8gaHVtaWxkZSBjdWVzdGEgdW4gMzUlIG1lbm9zLCBlbiBwcm9tZWRpbywgcXVlIHVuYSBwcm9waWVkYWQgcHJvdmVuaWVudGUgZGUgdW4gYmFycmlvIG3DoXMgcmljby4KCkFuYWxpY2Vtb3MgYWhvcmEgZWwgY29lZmljaWVudGUgZGUgUsKyIGFqdXN0YWRvIGRlIGVzdGUgbW9kZWxvIHkgY29tcGFyZW1vcyBjb24gbG9zIG1vZGVsb3MgYW50ZXJpb3Jlcy4KCmBgYHtyfQptb2RlbHMgPC0gbGlzdChtb2RlbG9fdG90YWwgPSBtb2RlbG9fdG90YWwsbW9kZWxvX3Npbl9sMyA9IG1vZGVsb19zaW5fbDMsIG1vZGVsb19iYXJyaW9zID0gbW9kZWxvX2JhcnJpb3MsIG1vZGVsb19kZXNjdWJpZXJ0YSA9IG1vZGVsb19kZXNjdWJpZXJ0YSwgbW9kZWxvX2xvZyA9IG1vZGVsb19sb2cpCmRmX2V2YWx1YWNpb25fdHJhaW4gPSBtYXBfZGYobW9kZWxzLCBicm9vbTo6Z2xhbmNlLCAuaWQgPSAibW9kZWwiKSAlPiUKICAjIG9yZGVuYW1vcyBwb3IgUjIgYWp1c3RhZG8KICBhcnJhbmdlKGRlc2MoYWRqLnIuc3F1YXJlZCkpCgpkZl9ldmFsdWFjaW9uX3RyYWluCgpgYGAKCkVsIFLCsiBhanVzdGFkbyBkZWwgbW9kZWxvIGNvbiBsb2dhcml0bW8gZXMgbcOhcyBhbHRvIGluY2x1c28gcXVlIGVsIFLCsiBhanVzdGFkbyBkZWwgbW9kZWxvIG9yaWdpbmFsIGRvbmRlIHVzYW1vcyB0b2RhcyBsYXMgdmFyaWFibGVzIHkgZGVsIG1vZGVsbyB1c2FuZG8gbGEgc3VwZXJmaWNpZSBkZXNjdWJpZXJ0YS4gRGUgZXN0byBjb25jbHVpbW9zIHF1ZSBlc3RlIG1vZGVsbyBleHBsaWNhIG1lam9yIGxhIHZhcmlhYmlsaWRhZCBkZWwgcHJlY2lvIHF1ZSBsb3MgbW9kZWxvcyBxdWUgY29uc3RydcOtbW9zIHByZXZpYW1lbnRlLiBDYWJlIHJlY29yZGFyLCBkZSB0b2RhcyBmb3JtYXMsIHF1ZSBlc3RlIG1vZGVsbyBlbiByZWFsaWRhZCBleHBsaWNhIGxhIHZhcmlhY2nDs24gZGVsIGxvZ2FyaXRtbyBkZWwgcHJlY2lvLCBwb3IgZW5kZSwgbcOhcyBxdWUgbGEgdmFyaWFjacOzbiBkZWwgcHJlY2lvIGVzdGFyw61hIGV4cGxpY2FuZG8gbGEgdmFyaWFjacOzbiBwcm9wb3JjaW9uYWwgZGVsIHByZWNpby4KCkFuYWxpY2Vtb3MgYWhvcmEgbGEgdmFsaWRleiBkZSBsb3Mgc3VwdWVzdG9zIGRlbCBtb2RlbG8gYW5hbGl6YW5kbyBsb3MgcmVzaWR1b3MgZGVsIG1pc21vLgoKYGBge3J9CnBsb3QobW9kZWxvX2xvZykKCmBgYApSZXNpZHVvcyB2cyB2YWxvcmVzIHByZWRpY2hvczogUG9kZW1vcyBlbmNvbnRyYXIgcG9jYSBlc3RydWN0dXJhIGVuIGxvcyByZXNpZHVvcy4gU2UgcHVlZGUgZGV0ZWN0YXIgcXVlIGxhIHZhcmlhY2nDs24gZGUgbG9zIHJlc2lkdW9zIHBhcmEgdmFsb3JlcyBwcmVkaWNob3MgYmFqb3MgKG1lbm9yZXMgYSAxMikgZXMgbcOhcyBiYWphIHF1ZSBwYXJhIHZhbG9yZXMgcHJlZGljaG9zIHViaWNhZG9zIGVuIGVsIGludGVydmFsbyBbMTIsMTNdLiBFbiBjb21wYXJhY2nDs24gYWwgbW9kZWxvIGRlIHN1cGVyZmljaWUgZGVzY3ViaWVydGEsIGVuIGVzdGUgbW9kZWxvIGVzdGFyw61hIG11Y2hvIG3DoXMgY2VyY2EgZGUgY3VtcGxpcnNlIGVsIHN1cHVlc3RvIGRlIGhvbW9jZWRhc3RpY2lkYWQuCgpRUSBwbG90OiBMb3MgZXh0cmFtb3MgaW5mZXJpb3IgaXpxdWllcmRvIHkgc3VwZXJpb3IgZGVyZWNobyBubyBzZSBhanVzdGFuIGEgbGEgZGlzdHJpYnVjacOzbiB0ZcOzcmljYS4gTGEgbm9ybWFsaWRhZCB0YW1wb2NvIHNlIGN1bXBsaXLDrWEgcG9yIGxvIHRhbnRvLiBBIGNvbXBhcmFjacOzbiBkZWwgbW9kZWxvIGRlIHN1cGVyZmljaWUgZGVzY3ViaWVydGEsIGxhIGRpc3RyaWJ1Y2nDs24gZXN0w6EgbXVjaG8gbcOhcyBjZXJjYSBkZSBsYSBkaXN0cmlidWNpw7NuIHRlw7NyaWNhLgoKUmVzaWR1b3MgZXN0YW5kYXJpemFkb3MgdnMgdmFsb3JlcyBwcmVkaWNob3M6IE5vIHNlIG9ic2VydmEgZXN0cnVjdHVyYSBlbiBsb3MgZGF0b3MuIAoKUmVzaWR1b3MgdnMgTGV2ZXJhZ2U6IEhheSAzIG9ic2VydmFjaW9uZXMgY29uIHVuIExldmVyYWdlIGFsdG8sIGF1bnF1ZSBubyBlc3TDoW4gZGVtYXNpYWRvIGxlam9zIGRlbCBsZXZlcmFnZSBkZSBvdHJhcyBvYnNlcnZhY2lvbmVzLCBhc8OtIHF1ZSBwb2Ryw61hbW9zIGRlc2VzdGltYXJsYXMuCgpEaWFnbsOzc3RpY286IEVsIG1vZGVsbyBkZSBsb2dhcml0bW8gcHJlc2VudGEgdW4gcG9jbyBkZSBoZXRlcm9jZWRhc3RpY2lkYWQgeSB0YW1wb2NvIHNhdGlzZmFjZSBlbCBzdXB1ZXN0byBkZSBub3JtYWxpZGFkLiBFc3RlIG1vZGVsbyBwb3IgbG8gdGFudG8gbm8gc2F0aXNmYWNlIGxvcyBzdXB1ZXN0b3MgZGVsIG1vZGVsbyBsaW5lYWwuICAKCiNNb2RlbG9zIGZpbmFsZXMKCkNyZWVtb3MgMiBtb2RlbG9zIG51ZXZvcyBwYXJhIGhhY2VyIHJlZ3Jlc2nDs24gbGluZWFsIG3Dumx0aXBsZS4gRGVzY3JpYmFtb3MgbnVlc3RybyBwcmltZXIgbW9kZWxvIHLDoXBpZGFtZW50ZQoKTW9kZWxvIHByb3BvcnRpb246IEVuIGVzdGUgbW9kZWxvIGVuIGx1Z2FyIGRlIGNvbnNpZGVyYXIgc3VwZXJmaWNpZV9kZXNjdWJpZXJ0YSBvIHN1cmZhY2VfY292ZXJlZCB0b21hcmVtb3MgZWwgcG9yY2VudGFqZSBkZSBzdXBlcmZpY2llIGN1YmllcnRhOiAxMDAgKiBTdXJmYWNlX2NvdmVyZWQvc3VyZmFjZV90b3RhbCB5IHN1cmZhY2VfdG90YWwgcGFyYSBhcm1hciB1biBtb2RlbG8uIEVzdG8gbG8gdXNhbW9zIGRlYmlkbyBhIHF1ZSBhIG51ZXN0cm8gZW50ZW5kZXIsIGN1YW50YSBtYXlvciBlcyBsYSBzdXBlcmZpY2llIGRlIHVuIHRlcnJlbm8sIG3DoXlvciBlcyBsYSBzdXBlcmZpY2llIGRlc2N1YmllcnRhLiBDb3Jyb2JvcmVtb3MgZXNhIGhpcMOzdGVzaXMgbWlyYW5kbyBsYSBjb3JyZWxhY2nDs24gZGUgc3BlYXJtYW4KCmBgYHtyfQpjb3IoeCA9IHByb3BlcnRpZXNfdHJhaW4kc3VyZmFjZV90b3RhbCxwcm9wZXJ0aWVzX3RyYWluJHN1cGVyZmljaWVfZGVzY3ViaWVydGEsIG1ldGhvZCA9ICdzcGVhcm1hbicpCmBgYAoKTGEgY29ycmVsYWNpw7NuIGRlIHNwZWFybWFuIGVzIGJhc3RhbnRlIG1heW9yIGEgMC4gU2kgdG9tYW1vcyBsYSBwcm9wb3JjacOzbiB0ZW5lbW9zLCBzaW4gZW1iYXJnbwoKYGBge3J9CmNvcih4ID0gcHJvcGVydGllc190cmFpbiRzdXJmYWNlX3RvdGFsLCB5ID0gMTAwKiBwcm9wZXJ0aWVzX3RyYWluJHN1cmZhY2VfY292ZXJlZC9wcm9wZXJ0aWVzX3RyYWluJHN1cmZhY2VfdG90YWwsIG1ldGhvZCA9ICdzcGVhcm1hbicpCgpgYGAKTGEgY29ycmVsYWNpw7NuIGVzIG5lZ2F0aXZhIHkgZXN0w6EgbcOhcyBjZXJjYSBkZWwgMCwgcG9yIGxvIHF1ZSBlcyB1bmEgY29ycmVsYWNpw7NuIG3DoXMgZMOpYmlsLgoKQ3JlZW1vcyBlbnRvbmNlcyBsYXMgZG9zIHZhcmlhYmxlcyBxdWUgbmVjZXNpdGFtb3MgcGFyYSBlc3RlIG1vZGVsbwoKYGBge3J9CnByb3BlcnRpZXNfdHJhaW4gPC0gcHJvcGVydGllc190cmFpbiAlPiUKICBtdXRhdGUocHJvcG9ydGlvbl9jb3ZlcmVkID0gMTAwKnN1cmZhY2VfY292ZXJlZC9zdXJmYWNlX3RvdGFsICkKYGBgCgpQcm9jZWRhbW9zIGFzw60gYSByZWFsaXphciBlbCBtb2RlbG8uIFVzYXJlbW9zIGxhcyBtaXNtYXMgdmFyaWFibGVzIHF1ZSBlbCBtb2RlbG8gZGUgYmFycmlvcyBzb2xvIHF1ZSBlbiB2ZXogZGUgc3VyZmFjZV9jb3ZlcmVkLCBzdXJwZXJmaWNpZV9kZXNjdWJpZXJ0YSB1c2FyZW1vcyBzdXJmYWNlX3RvdGFsIHkgcHJvcG9ydGlvbl9jb3ZlcmVkLiAKCmBgYHtyfQptb2RlbG9fcHJvcG9ydGlvbjwtIGxtKHByaWNlIH4gIHJvb21zICsgYmF0aHJvb21zICsgc3VyZmFjZV90b3RhbCArIHByb3BvcnRpb25fY292ZXJlZCArIHByb3BlcnR5X3R5cGUgKyBiYXJyaW9zLCBkYXRhID0gcHJvcGVydGllc190cmFpbikKdGlkeV9wcm9wb3J0aW9uIDwtIHRpZHkobW9kZWxvX3Byb3BvcnRpb24sIGNvbmYuaW50ID0gVFJVRSkKdGlkeV9wcm9wb3J0aW9uCnRpZHkoYW5vdmEobW9kZWxvX3Byb3BvcnRpb24pKQpgYGAKCgpMb3MgcC12YWxvcmVzIHJlc3BlY3Rpdm9zIGRlIGNhZGEgZXN0aW1hZG9yIHNvbiB0b2RvcyBtdXkgcGVxdWXDsW9zIHkgbG9zIGludGVydmFsb3MgZGUgY29uZmlhbnphIGVzdMOhbiBtdXkgbGVqYW5vcyBkZWwgMC4gTG9zIHAtdmFsb3JlcyBkZSBsYSB0YWJsYSBkZSBhbm92YSBkYW4gcHLDoWN0aWNhbWVudGUgMCwgZMOhbmRvbGUgdG9kbyBlc3RvIHZhbGlkZXogYSBudWVzdHJvIG1vZGVsby4gCgpSZXNwZWN0byBhIGxvcyBlc3RpbWFkb3JlcyBkZSBjYWRhIHZhcmlhYmxlLCBkZXN0YWNhbW9zIHF1ZSBlbCBlc3RpbWFkb3IgZGVsIGJldGEgZGUgcm9vbXMgZGEgbnVldmFtZW50ZSBuZWdhdGl2by4gRXN0bywgbnVldmFtZW50ZSwgc2UgZGViZSBhIHF1ZSwgcG9yIGxhIGNvbGluZWFsaWRhZCBkZSBsYXMgdmFyaWFibGVzLCBhbCBhdW1lbnRhciBlbCBuw7ptZXJvIGRlIGhhYml0YWNpb25lcyBkZWJlcsOtYSBhdW1lbnRhciBsYSBzdXBlcmZpY2llIGN1YmllcnRhLCB5IHBvciBlbmRlLCBsYSBwcm9wb3JjacOzbiBkZSBzdXBlcmZpY2llIGN1YmllcnRhIGRlbCB0ZXJlbm8uICAKCkVsIGVzdGltYWRvciBhc29jaWFkbyBhIGxhIHZhcmlhYmxlIHByb3BvcnRpb25fY292ZXJlZCBlcyAxMzM1LiBFc3RvIHNpZ25pZmljYSBxdWUsIGRlIGF1bWVudGFyIGxhIHByb3BvcmNpw7NuIGRlIHN1cGVyZmljaWUgY3ViaWVydGEgZW4gdW4gMSUgZGVsIHRvdGFsIGRlIGxhIHN1cGVyZmljaWUsIGVsIHByZWNpbyBkZSB1bmEgcHJvcGllcmRhZCBhdW1lbnRhcsOhLCBlbiBwcm9tZWRpbywgMTMzNSBkb2xhcmVzLgoKVmVhbW9zIHLDoXBpZGFtZW50ZSBhaG9yYSBzaSBzZSBjdW1wbGVuIGxvcyBzdXB1ZXN0b3MgZGVsIG1vZGVsby4KCmBgYHtyfQpwbG90KG1vZGVsb19wcm9wb3J0aW9uKQoKYGBgClJlc2lkdW9zIHZzIHZhbG9yZXMgcHJlZGljaG9zOiBTZSBwdWVkZSB2ZXIgdW5hIGVzdHJ1Y3R1cmEgZW4gbG9zIHJlc2lkdW9zLiBMYSB2YXJpYW56YSBkZSBsb3MgbWlzbW9zIGF1bWVudGEgY3VhbnRvIG1heW9yZXMgc29uIGxvcyB2YWxvcmVzIHByZWRpY2hvcy4KClFRIHBsb3Q6IExvcyBleHRyZW1vcyBubyBjb2luY2lkZW4gY29uIGxhIGRpc3RyaWJ1Y2nDs24gdGXDs3JpY2EKClJlc2lkdW9zIHZzIExldmVyYWdlOiBIYXkgdW5hIG9ic2VydmFjacOzbiBjb24gdW4gTGV2ZXJhZ2UgbXV5IGFsdG8gKG1heW9yIGEgMC4wMDMwKS4gSGF5IG90cmEgb2JzZXJ2YWNpw7NuIGNvbiB1biBMZXZlcmFnZSBiYXN0YW50ZSBhbHRvLgoKRGlhZ27Ds3N0aWNvOiBObyBzZSBzYXRpc2ZhY2VuIGxvcyBzdXB1ZXN0b3MgZGUgaG9tb2NlZGFzdGljaWRhZCBuaSBlbCBkZSBub3JtYWxpZGFkLCB5IGhheSB1bmEgb2JzZXJ2YWNpw7NuIGNvbiB1biBsZXZlcmFnZSBtdXkgc3VwZXJpb3IgYWwgcmVzdG8uIFBvciBsbyBxdWUgY29uY2x1aW1vcyBxdWUgbm8gc2Ugc2F0aXNmYWNlbiBsb3Mgc3VwdWVzdG9zIGRlbCBtb2RlbG8gbGluZWFsLgoKQW50ZXMgZGUgbWVkaXIgZWwgUsKyIGFqdXN0YWRvLCBjcmVlbW9zIG51ZXN0cm8gw7psdGltbyBtb2RlbG8uIEVzdGUgbW9kZWxvIHVzYXLDoSBvdHJhIHZleiBlbCBsb2dhcml0bW8gbmF0dXJhbCwgcGVybyBsZSBzdW1hcsOhIDEgYSBsYXMgdmFyaWFibGVzIHBhcmEgYXPDrSBwb2RlciBhcGxpY2FyIGVsIGxvZ2FyaXRtbyBpbmNsdXNvIGN1YW5kbyBsYXMgdmFyaWFibGVzIGFsY2FuY2VuIGVsIHZhbG9yIDAuIERlZmluYW1vcyBlbCBsb2dhcml0bW8gY29ycmlkbyBxdWUgdXNhcmVtb3MgcGFyYSB0cmFuZm9ybWFyIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcy4KCgoKYGBge3J9CmxvZ2MgPC0gZnVuY3Rpb24oeCl7cmV0dXJuIChsb2coeCsxKSl9CmBgYApMYXMgdmFyaWFibGVzIHF1ZSB2YW1vcyBhIHRyYW5zZm9ybWFyIHNvbiBwcmljZSwgcm9vbXMsIGJhdGhyb29tcyB5IHN1cmZhY2VfdG90YWwuIExhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIGJhcnJpb3MgeSBwcm9wZXJ0eV90eXBlIHkgbGEgdmFyaWFibGUgcHJvcG9ydGlvbl9jb3ZlcmVkIHNlcsOhbiB1dGlsaXphZGFzIGNvbiBzdXMgdmFsb3JlcyBvcmlnaW5hbGVzLgoKYGBge3J9CnByb3BlcnRpZXNfdHJhaW4gPC0gcHJvcGVydGllc190cmFpbiAlPiUKICBtdXRhdGUobG9nY19wcmljZSA9IGxvZ2MocHJpY2UpLCBsb2djX3Jvb21zID0gbG9nYyhyb29tcyksIGxvZ2NfYmF0aHJvb21zID0gbG9nYyhiYXRocm9vbXMpLCAgbG9nY19zdXJmYWNlX3RvdGFsCiAgICAgICAgID1sb2djKHN1cmZhY2VfdG90YWwpKQpgYGAKClByb2NlZGFtb3MgZW50b25jZXMgYSBlbnRyZW5hciBlbCBtb2RlbG8geSBvYnNlcnZlbW9zIGxvcyByZXN1bHRhZG9zIG9idGVuaWRvcy4KCgpgYGB7cn0KbW9kZWxvX2xvZ2M8LSBsbShsb2djX3ByaWNlIH4gIGxvZ2Nfcm9vbXMgKyBsb2djX2JhdGhyb29tcyArIGxvZ2Nfc3VyZmFjZV90b3RhbCArIHByb3BvcnRpb25fY292ZXJlZCArIHByb3BlcnR5X3R5cGUgKyBiYXJyaW9zLCBkYXRhID0gcHJvcGVydGllc190cmFpbikKdGlkeV9sb2djIDwtIHRpZHkobW9kZWxvX2xvZ2MsIGNvbmYuaW50ID0gVFJVRSkKdGlkeV9sb2djCnRpZHkoYW5vdmEobW9kZWxvX2xvZ2MpKQpgYGAKTG9zIHAtdmFsb3JlcyByZXNwZWN0aXZvcyBhIGNhZGEgdmFyaWFibGUgZGVsIG1vZGVsbyBzb24gc2lnbmlmaWNhdGl2YW1lbnRlIHBlcXVlbG9zIHkgbG9zIGludGVydmFsb3MgZGUgY29uZmlhbnphIG5vIGNvbnRpZW5lbiBhbCAwLiBMb3MgcC12YWxvcmVzIGRlIGxhIHRhYmxhIGRlIGFub3ZhIGRhbiBwcsOhY3RpY2FtZW50ZSAwLCBwb3IgbG8gcXVlIHRvZGFzIGxhcyB2YXJpYWJsZXMgc29uIHNpZ25pZmljYXRpdmFzIHBhcmEgZWwgbW9kZWxvLgoKUmVzcGVjdG8gYSBsb3MgZXN0aW1hZG9yZXMsIGxhIMO6bmljYSB2YXJpYWJsZSBudW3DqXJpY2EgcXVlIGRpbyBuZWdhdGl2byBlbCBlc3RpbWFkb3IgZXMsIG51ZXZhbWVudGUsIGxhIHZhcmlhYmxlIHJlbGFjaW9uYWRhIGNvbiByb29tcywgZW4gZXN0ZSBjYXNvIGxvZ2Nfcm9vbXMuIAoKVmVhbW9zIGFob3JhIHNpIHNlIHNhdGlzZmFjZW4gbG9zIHN1cHVlc3RvcyBkZWwgbW9kZWxvCgpgYGB7cn0KcGxvdChtb2RlbG9fbG9nYykKYGBgCgpEaWFnbsOzc3RpY286IFBhcmEgYWhvcnJhciBleHBsaWNhY2nDs24sIGxvcyBncsOhZmljb3MgbW9zdHJhZG9zIHNvbiBtdXkgc2ltaWxhcmVzIGEgbG9zIGdyw6FmaWNvcyBkZWwgb3RybyBtb2RlbG8gZGVsIGxvZ2FyaXRtbyBxdWUgYXJtYW1vcy4gUG9yIGVuZGUsIGxvcyByZXNpZHVvcyBwcmVzZW50YW4gdW5hIGxpZ2VyYSBoZXRlcm9jZWRhc3RpY2lkYWQgeSBubyBjdW1wbGVuIGVsIHN1cHVlc3RvIGRlIG5vcm1hbGlkYWQuIEhheSBhbGd1bmFzIG9ic2VydmFjaW9uZXMgY29uIGJhc3RhbnRlIExldmVyYWdlIHBlcm8gZWwgdmFsb3IgZGUgbG9zIExldmVyYWdlcyBubyBlcyBkZW1hc2lhZG8gZWxldmFkbyBkZSB0b2RvcyBtb2Rvcy4gUG9yIGxvIHRhbnRvLCBlbCBtb2RlbG8gY29uIGVsIGxvZ2FyaXRtbyBjb3JyaWRvIG5vIGN1bXBsZSBsb3Mgc3VwdWVzdG9zIGRlbCBtb2RlbG8gbGluZWFsLgoKT3RybyBwcm9ibGVtYSBkZSBlc3RlIG1vZGVsbyBlcyBsYSBpbnRlcnByZXRhYmlsaWRhZCBkZSBzdXMgcmVzdWx0YWRvcy4gQ29uIGVsIGxvZ2FyaXRtbyB5YSBkZSBwb3Igc8OtIG5vIGVzIGbDoWNpbCBoYWNlciB1bmEgaW50ZXJwcmV0YWNpw7NuIHNlbmNpbGxhLCBwZXJvIGFsIGNvcnJlciBlbCBsb2dhcml0bW8gaGF5IHF1ZSB0ZW5lciBlbiBjdWVudGEgZWwgY29ycmltaWVudG8gYSBsYSBob3JhIGRlIGludGVycHJldGFyLiAKClBvciBlamVtcGxvLCBlbiBsb2djX2JhdGhyb29tcyBlbCBlc3RpbWFkb3IgZGUgbGEgcGVuZGllbnRlIGRlbCBsb2dhcml0bW8gY29ycmlkbyBkZWwgcHJlY2lvIGVzCTAuMjgzMTQzNTIuIEVzdG8gc2lnbmlmaWNhIHF1ZSwgZGUgYXVtZW50YXIgZW4gMSBlbiB2YWxvciBkZWwgbG9nYXJpdG1vIGNvcnJpZG8gZGUgYmF0aHJvb21zLCBlbnRvbmNlcyBlbCBsb2dhcml0bW8gZGVsIHByZWNpbyBhdW1lbnRhcsOhLCBlbiBwcm9tZWRpbywgMC4yODMuIFBhcmEgcXVlIGF1bWVudGUgZW4gMSBlbCBsb2dhcml0bW8gY29ycmlkbywgbmVjZXNpdGFtb3MgcXVlIGVsIHZhbG9yIGRlIGxhIHZhcmlhYmxlIGNvcnJpZGEgc2UgbXVsdGlwbGlxdWUgcG9yIGUuIFJlY29yZGVtb3MgcXVlIDExIGVyYSwgYXByb3hpbWFkYW1lbnRlLCA0ICogZS4gTHVlZ28sIHNpIHRlbmVtb3MgdW5hIHByb3BpZWRhZCBjb24gMyBiYcOxb3MgeSBvdHJhIGNvbiAxMCwgdGVuZW1vcyBxdWUgKDMrMSkgKiBlIGVzIGFwcm94aW1hZGFtZW50ZSAxMCsxLiBQb3IgbG8gdGFudG8sIGxvZyAoIDEwICsgMSkgPSBsb2coMyArIDEpICsgMS4gRXN0byBub3MgZGljZSBxdWUsIHNpIHRlbmVtb3MgZG9zIHByb3BpZWRhZGVzIHNpbWlsYXJlcywgc29sbyBxdWUgdW5hIHRpZW5lIDEwIGJhw7FvcyB5IGxhIG90cmEgY29uIDMsIGVudG9uY2VzIGVsIGxvZ2FyaXRtbyBjb3JyaWRvIGRlbCBwcmVjaW8gZGUgbGEgcHJvcGllZGFkIGNvbiAxMCBiYcOxb3MgYXVtZW50YXLDoSwgcmVzcGVjdG8gZGVsIGxvZ2FyaXRtbyBjb3JyaWRvIGRlbCBwcmVjaW8gZGUgbGEgcHJvcGllZGFkIGNvbiAzIGJhw7FvcywgZW4gcHJvbWVkaW8sIGVuIDAuMjgzMS4gCgpMbGFtZW1vcyBBIGFsIHByZWNpbyBlc3RpbWFkbyBkZSBsYSBwcm9waWVkYWQgZGUgMTAgYmHDsW9zIHkgQiBhbCBwcmVjaW8gZXN0aW1hZG8gZGUgbGEgcHJvcGllZGFkIGNvbiAzIGJhw7FvcywgdGVuZW1vcyBlbnRvbmNlcyBxdWUgbG9nKEErMSkgPSBsb2coQisxKSArIDAuMjgzMQpMdWVnbyAoQSsxKS8oQisxKSA9IGVeKDAuMjgzMSkgPSAxLjMyCgpEZXNwZWphbmRvLCBBID0gMS4zMiAqIEIgKyAwLjMyCgpFcyBkZWNpciwgZXNwZXJhbW9zIHF1ZSBlbCBwcmVjaW8gZGUgbGEgcHJvcGllZGFkIGNvbiAxMCBoYWJpdGFjaW9uZXMgc2VhIGVsIHByZWNpbyBkZSBsYSBwcm9waWVkYWQgY29uMyBoYWJpdGFjaW9uZXMgbXVsdGlwbGljYWRvIHBvciAxLjMyIG3DoXMgMC4zMiAocXVlIGVzIGRlc3ByZWNpYWJsZSkuCgpFbCBwcm9jZXNvIHBhcmEgbGxlZ2FyIGEgZXN0YSBjb25jbHVzacOzbiBmdWUgdW5hIGN1ZW50YSBtdXkgdGVkaW9zYSB5IHR1dmltb3MgcXVlIGZpamFyIHVuIGVqZW1wbG8gcGFydGljdWxhciBwYXJhIGhhY2VyIHRvZG8gbcOhcyBjb21wcmVuc2libGUsIHBvciBsbyBxdWUgbGEgaW50ZXJwcmV0YWNpw7NuIGRlIGVzdGUgbW9kZWxvIGVzIG3DoXMgY29tcGxpY2FkYSBpbmNsdXNvIHF1ZSBsYSBpbnRlcnByZXRhY2nDs24gZGVsIHByaW1lciBtb2RlbG8gbG9nYXLDrXRtaWNvLgoKVmFtb3MgYSBidXNjYXIgZWwgbWVqb3IgbW9kZWxvLiBQYXJhIGVzbyBwcmltZXJvIHNlbGVjY2lvbmFyZW1vcyA0IG1vZGVsb3M6IGxvcyBkb3MgbW9kZWxvcyBxdWUgcmVjacOpbiBjcmVhbW9zLCB5IGRvcyBtb2RlbG9zIGRlIGxvcyBxdWUgdGVuw61hbW9zIHByZXZpYW1lbnRlLiBVbm8gZGUgZXNvcyBtb2RlbG9zIHNlcsOhIGVsIG1vZGVsbyBvcmlnaW5hbCBkZSBsb2dhcml0bW8sIHF1ZSBub3MgIGhhYsOtYSBkYWRvIGVsIFLCsiBhanVzdGFkbyBtw6FzIGdyYW5kZSwgeSBwb3IgZW5kZSBlcyBlbCBtb2RlbG8gcXVlIG1lam9yIGV4bHBpY2EgbGEgdmFyaWFiaWxpZGFkIGVudHJlIGxvcyBtb2RlbG9zIG9yaWdpbmFsZXMgKGF1bnF1ZSByZWNvcmRlbW9zIHF1ZSBlc2UgUsKyIG5vIHNlIGNvcnJlc3BvbmRlIGVzcGVjw61maWNhbWVudGUgYSBsYSB2YXJpYWJpbGlkYWQgZGVsIHByZWNpbykuIEVsIG90cm8gbW9kZWxvIHNlcsOhIGVsIG1vZGVsbyBkZSBzdXBlcmZpY2llIGRlc2N1YmllcnRhLCBxdWUsIHNpIGJpZW4gcG9zZWUgdW4gY29lZmljaWVudGUgZGUgUsKyIGFqdXN0YWRvIG3DoXMgY2hpY28gcXVlIGVsIGRlbCBsb2dhcml0bW8geSBxdWUgZWwgbW9kZWxvIGRvbmRlIGNvbnNpZGVyYW1vcyB0b2RhcyBsYXMgdmFyaWFibGVzLCBlcyB1biBtb2RlbG8gbXVjaG8gbcOhcyBzaW1wbGUgeSBmw6FjaWwgZGUgaW50ZXJwcmV0YXIgcXVlIGVzdG9zIDIgw7psdGltb3MuCgoKCkNvbXBhcmVtb3MgbGEgcGVyZm9ybWFuY2UgZGUgY2FkYSB1bm8gZGUgbG9zIDQgbW9kZWxvcyBzZWxlY2Npb25hZG9zIGVuIHTDqXJtaW5vcyBkZSBsYSBwcm9wb3JjacOzbiBkZSB2YXJpYWJpbGlkYWQgZXhwbGljYWRhLiBQYXJhIGVzbywgY29tcGFyYW1vcyBsb3MgY29lZmljaWVudGVzIGRlIFLCsiBhanVzdGFkby4KCmBgYHtyfQptb2RlbHMgPC0gbGlzdChtb2RlbG9fZGVzY3ViaWVydGEgPSBtb2RlbG9fZGVzY3ViaWVydGEsIG1vZGVsb19sb2cgPSBtb2RlbG9fbG9nLCBtb2RlbG9fcHJvcG9ydGlvbiA9IG1vZGVsb19wcm9wb3J0aW9uLCBtb2RlbG9fbG9nYyA9IG1vZGVsb19sb2djKQpkZl9ldmFsdWFjaW9uX3RyYWluID0gbWFwX2RmKG1vZGVscywgYnJvb206OmdsYW5jZSwgLmlkID0gIm1vZGVsIikgJT4lCiAgIyBvcmRlbmFtb3MgcG9yIFIyIGFqdXN0YWRvCiAgYXJyYW5nZShkZXNjKGFkai5yLnNxdWFyZWQpKQoKZGZfZXZhbHVhY2lvbl90cmFpbgoKYGBgCgoKRWwgbW9kZWxvX2xvZ2MgdGllbmUgdW4gY29lZmljaWVudGUgZGUgUsKyIGFqdXN0YWRvIHVuIHBvY28gbWF5b3IgcXVlIGVsIG1vZGVsb19sb2cKCiNFdmFsdWFjacOzbiBkZSBsb3MgbW9kZWxvcyBlbiBlbCBkYXRhc2V0IGRlIHRyYWluaW5nCgpWYW1vcyBhaG9yYSBhIHByb2NlZGVyIGEgZXZhbHVhciBsb3MgNCBtb2RlbG9zIHF1ZSBzZWxlY2Npb25hbW9zIHBhcmEgdmVyIGN1YWwgZXMgZWwgbWVqb3IuIFBhcmEgaGFjZXIgbGEgZXZhbHVhY2nDs24gdmFtb3MgYSB1dGlsaXphciBlbCBlcnJvciBjdWFkcsOhdGljbyBtZWRpbywgZWwgUk1TRS4gRWwgbW9kZWxvIGxpbmVhbCBidXNjYSBlc3RpbWFyIGxvcyBjb2VmaWNpZW50ZXMgcGFyYSBtaW5pbWl6YXIgZWwgZXJyb3IgY3VhZHLDoXRpY28gbWVkaW8sIHBvciBlbmRlIHRlbmRyw61hIHNlbnRpZG8gdXRpbGl6YXIgZXN0YSBtw6l0cmljYSBwYXJhIGV2YWx1YXIgbGEgcGVyZm9ybWFuY2UgZGUgbG9zIG1vZGVsb3MuIERlIHRvZG9zIG1vZG9zLCBlc3RhIG3DqXRyaWNhIHRlbmRyw61hIG3DoXMgc2VudGlkbyBlbiBlbCBjb25qdW50byBkZSB0ZXN0aW5nLCB5YSBxdWUgcG9kZW1vcyBtaW5pbWl6YXIgZWwgdmFsb3IgZGUgZXN0YSBtw6l0cmljYSBzaW1wbGVtZW50ZSBoYWNpZW5kbyBtw6FzIGNvbXBsZWpvIGFsIG1vZGVsbyB5IGFncmVnYW5kbyBsYSBtYXlvciBjYW50aWRhZCBkZSB2YXJpYWJsZXMgcG9zaWJsZXMgYWwgbWlzbW8uCgpVbiBpbmNvbnZlbmllbnRlIGRlIGVzdGEgbcOpdHJpY2EgZXMgcXVlIG5vIGVzIG11eSByb2J1c3RhIGFudGUgbGEgcHJlc2VuY2lhIGRlIG91dGxpZXJzLiBEZSB0ZW5lciB1biBkYXRhc2V0IGNvbiB1biBncmFuIG7Dum1lcm8gZGUgb3V0bGllcnMgZGViZXLDrWFtb3MgaGFiZXIgaGVjaG8gdW4gcHJlcHJvY2VzYW1pZW50byBwYXJhIGxpbXBpYXIgbG9zIGRhdG9zIHkgYXPDrSBwb2RlciByZWFsaXphciBtb2RlbG9zIGxpbmVhbGVzIHF1ZSBubyBzZSB2ZWFuIG11eSBhZmVjdGFkb3MgcG9yIGxhcyBvYnNlcnZhY2lvbmVzIGNvbiBhbHRvIGxldmVyYWdlIHF1ZSByZXN1bHRhbiBvdXRsaWVycy4KCk90cm8gaW5jb252ZW5pZW50ZSBxdWUgdGVuZW1vcyBlcyBxdWUgcGFyYSBoYWNlciBsYSB2YWxpZGFjacOzbiB0b2RvcyBsb3MgbW9kZWxvcyBsaW5lYWxlcyBkZWJlcsOtYW4gcHJlZGVjaXIgZWwgcHJlY2lvLiBTaW4gZW1iYXJnbyBsb3MgbW9kZWxvcyBjb24gbG9nYXJpdG1vIHByZWRpY2VuIHVuYSBjb21wb3NpY2nDs24gZGVsIHByZWNpbyBjb24gdW5hIGZ1bmNpw7NuIGxvZ2FyaXRtaWNhLiBMYSBmdW5jacOzbiBhdWdtZW50IG5vcyBkYSBsb3MgdmFsb3JlcyBwcmVkaWNob3MgeSBsb3MgcmVzaWR1b3MuIFBhcmEgY29tcGFyYXIgbGEgcGVyZm9ybWFjZSBkZSBsb3MgbW9kZWxvcyBjb252ZW5kcsOtYSwgZW4gbG9zIG1vZGVsb3MgbG9nYXJpdG1pY29zLCB0cmFuc2Zvcm1hciBsb3MgdmFsb3JlcyBwcmVkaWNob3MgcGFyYSBxdWUgcHJlZGlnYW4gZWwgcHJlY2lvLCB5IGFjdHVhbGl6YXIgbG9zIHJlc2lkdW9zIGNvbiBlc2EgaW5mb3JtYWNpw7NuLgoKQ29tZW5jZW1vcyBjb24gZWwgcHJpbWVyIG1vZGVsbyBsb2dhcsOtdG1pY28gcXVlIGFybWFtb3MuIEVuIGVzZSBtb2RlbG8gZXhwbGljYW1vcyBsYSB2YXJpYWNpw7NuIGRlIGxvZyhwcmljZSkgcmVzcGVjdG8gYSBsYXMgb3RyYXMgdmFyaWFibGVzLiBQYXJhIG9idGVuZXIgdW5hIHByZWRpY2Npw7NuIGRlbCBwcmVjaW8gcG9kZW1vcyBjb21wb25lciBjb24gbGEgZnVuY2nDs24gaW52ZXJzYSBkZWwgbG9nYXJpdG1vOiBsYSBmdW5jacOzbiBleHBvbmVuY2lhbC4gVW5hIHZleiBoZWNoYSBlc2EgY29udmVyc2nDs24sIHRlbmVtb3MgdGFtYmnDqW4gcXVlIGFjdHVhbGl6YXIgZWwgcmVzaWR1bywgcXVlIHJlc3VsdGEgc2VyIGxhIGRpZmVyZW5jaWEgZW50cmUgZWwgdmFsb3IgcmVhbCwgZWwgcHJlY2lvLCB5IGVsIG51ZXZvIHZhbG9yIGFqdXN0YWRvLgoKCgoKYGBge3J9CnByZWRfbG9nIDwtIGF1Z21lbnQobW9kZWxvX2xvZywgbmV3ZGF0YT1wcm9wZXJ0aWVzX3RyYWluKSAlPiUKICBtdXRhdGUoLmZpdHRlZCA9IGV4cCguZml0dGVkKSwgLnJlc2lkID0gcHJpY2UtLmZpdHRlZCkgJT4lCiAgc2VsZWN0KGlkLCBwcmljZSwgLmZpdHRlZCwgLnJlc2lkKQpwcmVkX2xvZwpgYGAKVmVhbW9zIGVsIGVycm9yIGN1YWRyw6F0aWNvIG1lZGlvIGRlbCBtb2RlbG8KYGBge3J9CnJtc2UoZGF0YSA9IHByZWRfbG9nLCB0cnV0aCA9IHByaWNlLCBlc3RpbWF0ZSA9IC5maXR0ZWQpCgpgYGAKCkhhZ2Ftb3MgbG8gbWlzbW8gY29uIGVsIG1vZGVsbyBkZSBzdXBlcmZpY2llIGRlc2N1YmllcnRhCgpgYGB7cn0KcHJlZF9kZXNjdWJpZXJ0YSA8LSBhdWdtZW50KG1vZGVsb19kZXNjdWJpZXJ0YSwgbmV3ZGF0YT1wcm9wZXJ0aWVzX3RyYWluKSAlPiUKICBzZWxlY3QoaWQsIHByaWNlLCAuZml0dGVkLCAucmVzaWQpCnByZWRfZGVzY3ViaWVydGEKYGBgCkNhbGN1bGVtb3Mgc3UgUk1TRQoKYGBge3J9CnJtc2UoZGF0YSA9IHByZWRfZGVzY3ViaWVydGEsIHRydXRoID0gcHJpY2UsIGVzdGltYXRlID0gLmZpdHRlZCkKCmBgYAoKRWwgUk1TRSBkaW8gbcOhcyBhbHRvLCB2ZWFtb3MgY29uIGVsIG1vZGVsbyBkb25kZSBpbmNsdcOtbW9zIGVsIHBvcmNlbnRhamUgZGUgc3VwZXJmaWNpZSBjdWJpZXJ0YS4KCmBgYHtyfQpwcmVkX3Byb3BvcnRpb24gPC0gYXVnbWVudChtb2RlbG9fcHJvcG9ydGlvbiwgbmV3ZGF0YT1wcm9wZXJ0aWVzX3RyYWluKSAlPiUKICBzZWxlY3QoaWQsIHByaWNlLCAuZml0dGVkLCAucmVzaWQpCnByZWRfcHJvcG9ydGlvbgpgYGAKQW5hbGljZW1vcyBlbCBSTVNFLgoKYGBge3J9CnJtc2UoZGF0YSA9IHByZWRfcHJvcG9ydGlvbiwgdHJ1dGggPSBwcmljZSwgZXN0aW1hdGUgPSAuZml0dGVkKQpgYGAKClBvciDDumx0aW1vIHByb2JlbW9zIGNvbiBlbCDDumx0aW1vIG1vZGVsbyBsb2dhcsOtdG1pY28gcXVlIGNyZWFtb3MuIEVuIGVzdGUgY2FzbyBjb21wdXNpbW9zIHByaWNlIGNvbiBsYSBmdW5jacOzbiBsbih4KzEpLiBMYSBpbnZlcnNhIGRlIGVzYSBmdW5jacOzbiBlcyBnKHgpID0gZXhwKHgpLTEuIEJhc8OhbmRvbm9zIGVuIGVzbyBhY3R1YWxpemFtb3MgbG9zIHZhbG9yZXMgcHJlZGljaG9zIHkgbG9zIHJlc2lkdW9zLgoKYGBge3J9CnByZWRfbG9nYyA8LSBhdWdtZW50KG1vZGVsb19sb2djLCBuZXdkYXRhPXByb3BlcnRpZXNfdHJhaW4pICU+JQogIG11dGF0ZSguZml0dGVkID0gZXhwKC5maXR0ZWQpIC0gMSwgLnJlc2lkID0gcHJpY2UtLmZpdHRlZCkgJT4lCiAgc2VsZWN0KGlkLCBwcmljZSwgLmZpdHRlZCwgLnJlc2lkKQpwcmVkX2xvZ2MKYGBgClZlYW1vcyBlbCBSTVNFIGRlIGVzdGUgbW9kZWxvIGxvZ2Fyw610bWljby4KCmBgYHtyfQpybXNlKGRhdGEgPSBwcmVkX2xvZ2MsIHRydXRoID0gcHJpY2UsIGVzdGltYXRlID0gLmZpdHRlZCkKYGBgCkVsIFJNU0UgZGVsIG1vZGVsbyBsb2dhcsOtdG1pY28gY29ycmlkbyBkaW8gbcOhcyBiYWpvIHF1ZSBlbiBsb3Mgb3Ryb3MgbW9kZWxvcywgY29uIHVuIGVycm9yIGN1YWRyw6F0aWNvIG1lZGlvIGRlIDY4Mzg0LjQxLiBSZWNvcmRlbW9zIHF1ZSBlc3RlIG1vZGVsbyBlcyBtw6FzIGRpZmljaWwgZGUgaW50ZXJwcmV0YXIgcXVlIGxvcyBtb2RlbG9zIHF1ZSBubyBpbnZvbHVjcmFuIGxvZ2FyaXRtby4gU2luIGVtYmFyZ28sIGVsIMO6bmljbyBtb2RlbG8gY29uIHVuIGVycm9yIGN1YWRyw6F0aWNvIG1lZGlvIGNlcmNhbm8gdGFtYmnDqW4gZXMgdW4gbW9kZWxvIGxvZ2Fyw610bWljbywgYXPDrSBxdWUgY29uc2lkZXJhbW9zLCBlbiB0w6lybWlub3MgZGVsIGRhdGFzZXQgZGUgZW50cmVuYW1pZW50bywgcXVlIGVsIG1vZGVsbyBkZSBsb2dhcml0bW8gY29ycmlkbyBlcyBlbCBtZWpvciBtb2RlbG8gZGUgbG9zIDQgcXVlIHNlbGVjY2lvbmFtb3MuIEFob3JhIGFuYWxpY2Vtb3MgY29tbyBkYW4gbGFzIG3DqXRyaWNhcyBlbiBlbCBkYXRhc2V0IGRlIHRlc3RpbmcuCgoKI0V2YWx1YWNpw7NuIGRlIGxvcyBtb2RlbG9zIGVuIGVsIGRhdGFzZXQgZGUgdGVzdGluZwoKUGFyYSBwb2RlciB2YWxpZGFyIGVuIHRlc3RpbmcgZGViZXLDrWFtb3MgcmVkZWZpbmlyIHRvZGFzIGxhcyB2YXJpYWJsZXMgcXVlIHVzYW1vcyBlbiBlc2UgbW9kZWxvLgoKUGFyYSB0cmFlciBkZSBudWV2byBsYSB2YXJpYWJsZSAnYmFycmlvcycsIHZhbW9zIGEgZXh0cmFlciBsYSBpbmZvcm1hY2nDs24gZGVsIGRhdGFzZXQgZGUgZW50cmVuYW1pZW50byBlbCBsdWdhciBkZSByZXBldGlyIGVsIHByb2NlZGltaWVudG8gZW4gZWwgZGF0YXNldCBkZSBleHBsb3JhY2nDs24sIGRlIGVzYSBmb3JtYSBldml0YW1vcyB1c2FyIGVsIHByZWNpbyBkZSBsYXMgcHJvcGllZGFkZXMgZGUgdGVzdGluZyBwYXJhIGRlZmluaXIgbGEgdmFyaWFibGUgYmFycmlvcyB5IGFkZW3DoXMgc29tb3MgY29uc2lzdGVudGVzIGEgbGEgZWxlY2Npw7NuIHF1ZSBoaWNpbW9zIGFudGVzLgoKYGBge3J9CmJhcnJpb19zZWxlY2Npb24gPC0gdW5pcXVlKHByb3BlcnRpZXNfdHJhaW4gJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KGwzLCBiYXJyaW9zKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKYmFycmlvX3NlbGVjY2lvbgpgYGAKSGFjZW1vcyB1biBsZWZ0IGpvaW4gcGFyYSBhbmV4YXIgbGEgdmFyaWFibGUgYmFycmlvcyBhbCBkYXRhc2V0IGRlIHRlc3RpbmcuCgpgYGB7cn0KcHJvcGVydGllc190ZXN0IDwtcHJvcGVydGllc190ZXN0ICU+JQogIGxlZnRfam9pbiguLCBiYXJyaW9fc2VsZWNjaW9uLCBieSA9ICJsMyIpCmhlYWQocHJvcGVydGllc190ZXN0KQpgYGAKYGBge3J9CnByb3BlcnRpZXNfdGVzdCA8LXByb3BlcnRpZXNfdGVzdCAlPiUKICBtdXRhdGUoc3VwZXJmaWNpZV9kZXNjdWJpZXJ0YSA9IHN1cmZhY2VfdG90YWwtc3VyZmFjZV9jb3ZlcmVkLCBwcm9wb3J0aW9uX2NvdmVyZWQgPSAxMDAqc3VyZmFjZV9jb3ZlcmVkL3N1cmZhY2VfdG90YWwsIGxvZ19wcmljZSA9IGxvZyhwcmljZSksIGxvZ19yb29tcyA9IGxvZyhyb29tcyksIGxvZ19iYXRocm9vbXMgPSBsb2coYmF0aHJvb21zKSwgbG9nX3N1cmZhY2VfY292ZXJlZCA9IGxvZyhzdXJmYWNlX2NvdmVyZWQpLCBsb2djX3ByaWNlID0gbG9nYyhwcmljZSksIGxvZ2Nfcm9vbXMgPSBsb2djKHJvb21zKSwgbG9nY19iYXRocm9vbXMgPSBsb2djKGJhdGhyb29tcyksIGxvZ2Nfc3VyZmFjZV90b3RhbCA9IGxvZ2Moc3VyZmFjZV90b3RhbCkpCmBgYAoKRXZhbHVlbW9zIGFob3JhIHPDrSBjYWRhIG1vZGVsbywgeSBlbmNvbnRyZW1vcyBlbCBSTVNFIGRlIGNhZGEgdW5vCgpQcmltZXIgbW9kZWxvIGxvZ2Fyw610bWljbwoKYGBge3J9CnByZWRfbG9nX3Rlc3QgPC0gYXVnbWVudChtb2RlbG9fbG9nLCBuZXdkYXRhPXByb3BlcnRpZXNfdGVzdCkgJT4lCiAgbXV0YXRlKC5maXR0ZWQgPSBleHAoLmZpdHRlZCksIC5yZXNpZCA9IHByaWNlLS5maXR0ZWQpCgpybXNlKHByZWRfbG9nX3Rlc3QsIHRydXRoID0gcHJpY2UsIGVzdGltYXRlID0gLmZpdHRlZCkKCmBgYApNb2RlbG8gZGUgc3VwZXJmaWNpZSBkZXNjdWJpZXJ0YQoKYGBge3J9CnByZWRfZGVzY3ViaWVydGFfdGVzdCA8LSBhdWdtZW50KG1vZGVsb19kZXNjdWJpZXJ0YSwgbmV3ZGF0YT1wcm9wZXJ0aWVzX3Rlc3QpCgpybXNlKHByZWRfZGVzY3ViaWVydGFfdGVzdCwgdHJ1dGggPSBwcmljZSwgZXN0aW1hdGUgPSAuZml0dGVkKQoKYGBgCk1vZGVsbyBkZSBjb24gcG9yY2VudGFqZSBkZSBzdXBlcmZpY2llIGN1YmllcnRhCgpgYGB7cn0KcHJlZF9wcm9wb3J0aW9uX3Rlc3QgPC0gYXVnbWVudChtb2RlbG9fcHJvcG9ydGlvbiwgbmV3ZGF0YT1wcm9wZXJ0aWVzX3Rlc3QpCgpybXNlKHByZWRfcHJvcG9ydGlvbl90ZXN0LCB0cnV0aCA9IHByaWNlLCBlc3RpbWF0ZSA9IC5maXR0ZWQpCgpgYGAKCgpNb2RlbG8gbG9nYXLDrXRtaWNvIGNvbiBjb3JyaW1pZW50bwoKYGBge3J9CnByZWRfbG9nY190ZXN0IDwtIGF1Z21lbnQobW9kZWxvX2xvZ2MsIG5ld2RhdGE9cHJvcGVydGllc190ZXN0KSAlPiUKICBtdXRhdGUoLmZpdHRlZCA9IGV4cCguZml0dGVkKS0xLCAucmVzaWQgPSBwcmljZS0uZml0dGVkKQoKcm1zZShwcmVkX2xvZ2NfdGVzdCwgdHJ1dGggPSBwcmljZSwgZXN0aW1hdGUgPSAuZml0dGVkKQoKYGBgCgpOdWV2YW1lbnRlLCBlbCBtb2RlbG8gY29uIGVycm9yIGN1YWRyw6F0aWNvIG1lZGlvIG3DoXMgY2hpY28gZXMgZWwgbW9kZWxvIGNvbiBsb2dhcml0bW8gY29ycmlkbywgc2VndWlkbyBwb3IgZWwgcHJpbWVyIG1vZGVsbyBsb2dhcsOtdG1pY28uIAoKRGFkbyBxdWUgZXN0ZSBtb2RlbG8gZXMgZWwgbWVqb3IgdGFudG8gZW4gZWwgY29uanVudG8gZGUgZW50cmVuYW1pZW50byBjb21vIGVuIGVsIGRlIHRlc3RpbmcsIHksIGFkZW3DoXMsIGVsIMO6bmljbyBtb2RlbG8gbWVkaWFuYW1lbnRlIGNlcmNhbm8gZW4gZXN0ZSBzZW50aWRvIGVzIGVsIG90cm8gbW9kZWxvIG1vZGVsbyBkaWZpY2lsIGRlIGludGVycHJldGFyLCBjb25jbHVpbW9zIHF1ZSBlbCBtb2RlbG8gY29uIGxvZ2FyaXRtbyBjb3JyaWRvIGVzIGVsIG1lam9yIG1vZGVsbyBjb24gbWVqb3IgcGVyZm9ybWFuY2UgeSBlbCBtw6FzIGFkZWN1YWRvIHBhcmEgZXhwbGljYXIgbGEgdmFyaWFjacOzbiBkZWwgcHJlY2lvLg==